Browse Source

--- Merging r13787 into '.':
U packages/fcl-xml/tests/domunit.pp
--- Merging r13788 into '.':
U packages/fcl-xml/tests/extras.pp
--- Merging r13789 into '.':
U packages/fcl-xml/src/xmlwrite.pp
U packages/fcl-xml/src/dom.pp
U packages/fcl-xml/src/xmlutils.pp
--- Merging r13800 into '.':
U packages/fcl-xml/tests/api.xml
G packages/fcl-xml/src/dom.pp
--- Merging r13809 into '.':
G packages/fcl-xml/src/dom.pp
U packages/fcl-xml/src/xmlread.pp
--- Merging r13816 into '.':
U packages/fcl-xml/src/xpath.pp
--- Merging r13817 into '.':
G packages/fcl-xml/src/dom.pp
G packages/fcl-xml/src/xmlutils.pp
--- Merging r13818 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r13824 into '.':
G packages/fcl-xml/src/dom.pp
--- Merging r13827 into '.':
G packages/fcl-xml/src/xpath.pp
--- Merging r13842 into '.':
G packages/fcl-xml/src/xpath.pp
--- Merging r13846 into '.':
U packages/fcl-xml/tests/xpathts.pp
G packages/fcl-xml/src/xpath.pp
--- Merging r13856 into '.':
G packages/fcl-xml/src/xmlwrite.pp
--- Merging r13858 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r13860 into '.':
U packages/fcl-xml/tests/xmlts.pp
--- Merging r13861 into '.':
G packages/fcl-xml/src/xmlutils.pp
G packages/fcl-xml/src/xmlread.pp
--- Merging r13878 into '.':
G packages/fcl-xml/tests/extras.pp
U packages/fcl-xml/tests/extras2.pp
--- Merging r13886 into '.':
G packages/fcl-xml/src/xmlutils.pp
--- Merging r13894 into '.':
G packages/fcl-xml/src/xmlwrite.pp
--- Merging r13906 into '.':
G packages/fcl-xml/src/xmlwrite.pp
--- Merging r13914 into '.':
G packages/fcl-xml/tests/extras2.pp
G packages/fcl-xml/src/xmlread.pp
--- Merging r13915 into '.':
G packages/fcl-xml/src/xpath.pp
--- Merging r13921 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r13922 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r13960 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r13961 into '.':
G packages/fcl-xml/src/xmlread.pp

Merged XML revisions before 14000

------------------------------------------------------------------------
r13787 | sergei | 2009-10-01 21:11:47 +0200 (Thu, 01 Oct 2009) | 2 lines

* r13729 was broken due to missing typecast, shame on me. Fixed.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13788 | sergei | 2009-10-01 21:14:36 +0200 (Thu, 01 Oct 2009) | 1 line

+ 3 more tests for verifying the namespace fixup
------------------------------------------------------------------------
------------------------------------------------------------------------
r13789 | sergei | 2009-10-01 21:29:13 +0200 (Thu, 01 Oct 2009) | 1 line

+ XML writer now performs the namespace normalization.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13800 | sergei | 2009-10-04 01:35:20 +0200 (Sun, 04 Oct 2009) | 4 lines

Two more DOM Level 3 functions + tests for them:
+ TDOMNode.lookupPrefix()
+ TDOMNode.isDefaultNamespaceURI()

------------------------------------------------------------------------
------------------------------------------------------------------------
r13809 | sergei | 2009-10-06 13:03:25 +0200 (Tue, 06 Oct 2009) | 2 lines

* TDOMDocument now checks its children and allows only a single Element/DocType child. This fixes about 8 test cases at Level 3.
+ Initial moves to implement TDOMNode.BaseURI (not yet functional)
------------------------------------------------------------------------
------------------------------------------------------------------------
r13816 | sergei | 2009-10-07 23:27:11 +0200 (Wed, 07 Oct 2009) | 1 line

* For some reason, any assignment of NaN cannot be compiled with overflow checking, issue #14748.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13817 | sergei | 2009-10-08 02:46:27 +0200 (Thu, 08 Oct 2009) | 1 line

* Fixed (runtime) overflows and range checks, so the package runs after compiling with OPT="-Criot -gtl". That Delphi compatibility patch is starting to cause more trouble than benefit...
------------------------------------------------------------------------
------------------------------------------------------------------------
r13818 | sergei | 2009-10-08 16:48:34 +0200 (Thu, 08 Oct 2009) | 4 lines

xmlread.pp:
* Report unclosed Ignore section as soon as it is detected, improves error messages and simplifies the code.
* Since SkipUntilSeq is only ever called with 1 or 2 delimiter chars, support just that rather than arbitrary-length array. Simplifies code.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13824 | sergei | 2009-10-09 17:37:15 +0200 (Fri, 09 Oct 2009) | 1 line

* Hunted down another node pool bug, thanks to Vincent Snijders and mighty valgrind. Due to wrong condition, the last block of extent wasn't used to put an object there, but later a cleanup attempt at that address was attempted.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13827 | sergei | 2009-10-09 19:47:53 +0200 (Fri, 09 Oct 2009) | 2 lines

xpath.pp: Fixed runtime range and overflow checks, at least those which could be detected with existing test suite.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13842 | sergei | 2009-10-11 01:57:39 +0200 (Sun, 11 Oct 2009) | 1 line

xpath.pp: cleaned up and simplified TXPathScanner.GetToken()
------------------------------------------------------------------------
------------------------------------------------------------------------
r13846 | sergei | 2009-10-11 17:04:33 +0200 (Sun, 11 Oct 2009) | 7 lines

xpath.pp:
* Progress with namespace support. Resolve namespace prefixed while parsing, compare namespaceURI/localNames while evaluating. Existing tests for namespace-uri(), local-name() and name() now all pass, but resolving interface isn't ready for general use yet.
* Fixed name() to default to context node if argument is omitted.

xpathts.pp:
+ support for prefix resolving while testing.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13856 | sergei | 2009-10-13 23:50:07 +0200 (Tue, 13 Oct 2009) | 1 line

xmlwrite.pp: c14n compliance: write whitespace after PI name only if its data is not empty
------------------------------------------------------------------------
------------------------------------------------------------------------
r13858 | sergei | 2009-10-15 00:12:27 +0200 (Thu, 15 Oct 2009) | 4 lines

xmlread.pp, refactoring of literal handling:
* separate procedure SkipQuote, reused by SkipQuotedLiteral and ExpectAttValue;
* inlined SkipPubidLiteral to the (only) place where it is called.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13860 | sergei | 2009-10-15 23:27:31 +0200 (Thu, 15 Oct 2009) | 1 line

* Only EXMLReadError is expected to be thrown from a negative test, any other exception is unexpected and should render the test as failed.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13861 | sergei | 2009-10-16 00:11:13 +0200 (Fri, 16 Oct 2009) | 1 line

xmlread.pp: now uses TNSSupport class from xmlutils unit instead of its own copy of the same code.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13878 | sergei | 2009-10-17 00:50:46 +0200 (Sat, 17 Oct 2009) | 1 line

Fixed memory leaks in tests.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13886 | sergei | 2009-10-17 13:02:01 +0200 (Sat, 17 Oct 2009) | 3 lines

* TNSSupport.CheckAttribute added check for null nsURI, it should not emit prefixes for namespaceless attributes.
* Reformatted and fixed comments.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13894 | sergei | 2009-10-17 14:30:04 +0200 (Sat, 17 Oct 2009) | 1 line

XML writer: write namespace declarations before attribites and optionally sort them as required by c14n.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13906 | sergei | 2009-10-18 00:09:20 +0200 (Sun, 18 Oct 2009) | 3 lines

* Changed TSpecialCharCallback from method into a regular procedure, this allows to drop all the {$ifdef fpc}@{$endif} ugliness.
* Also changed TSpecialCharCallback to take the string and the index, so it can process certain sequences, not only single chars.
* In canonical mode, CDATA sections are written as text.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13914 | sergei | 2009-10-21 00:09:51 +0200 (Wed, 21 Oct 2009) | 2 lines

+ Initial implementation of property TDOMParseOptions.CanonicalForm. The only thing it does yet is ignoring the DTD.
* Fixed relevant tests.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13915 | sergei | 2009-10-21 00:13:13 +0200 (Wed, 21 Oct 2009) | 4 lines

xpath.pp:
* text() selector matches text and CDATA nodes, but not comments
* Names of processing instructions are now matched as they should.

------------------------------------------------------------------------
------------------------------------------------------------------------
r13921 | sergei | 2009-10-23 01:41:24 +0200 (Fri, 23 Oct 2009) | 4 lines

Straightening handling of base URI and SystemID:
- TXMLCharSource.PublicID removed, it is unused.
* Base URI of an entity is stored in FURI field of entity, and passed to ResolveEntity.
* When error happens while parsing an internal entity, report the URI where that entity was declared, not where it was included.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13922 | sergei | 2009-10-23 06:16:15 +0200 (Fri, 23 Oct 2009) | 2 lines

* Fixed the order of checks during parsing the xml declaration, this provides more accurate diagnostic for tests dtd07, encoding07, ibm77n03.
* Delay switching to xml 1.1 rules until the declaration has been parsed, this ensures that NEL and LSEP chars in declaration are rejected (rmt-056, rmt-057).
------------------------------------------------------------------------
------------------------------------------------------------------------
r13960 | sergei | 2009-10-26 20:17:54 +0100 (Mon, 26 Oct 2009) | 1 line

* Fixed bug in length calculation, causing any namespaced attributes with local part differing only in the last character (or consisting of a single character), to be erroneously reported as duplicate.
------------------------------------------------------------------------
------------------------------------------------------------------------
r13961 | sergei | 2009-10-26 22:23:24 +0100 (Mon, 26 Oct 2009) | 4 lines

xmlread.pp, tighten up checks while parsing the xml declaration:
* Hard limit of literal lengths: 3 on version, 30 on encoding name, 2 or 3 on standalone. Without this, a misplaced quote could cause excessive amount of processing, because input buffer is reloaded by small 3-char chunks at this time.
* Encoding validity is checked in-line, the very first illegal character aborts processing.

------------------------------------------------------------------------

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

marco 15 years ago
parent
commit
7ef87a6d7e

+ 152 - 31
packages/fcl-xml/src/dom.pp

@@ -101,7 +101,7 @@ type
   TDOMAttrDef = class;
   TDOMAttrDef = class;
   TNodePool = class;
   TNodePool = class;
   PNodePoolArray = ^TNodePoolArray;
   PNodePoolArray = ^TNodePoolArray;
-  TNodePoolArray = array[0..0] of TNodePool;
+  TNodePoolArray = array[0..MaxInt div sizeof(Pointer)-1] of TNodePool;
 
 
 {$ifndef fpc}
 {$ifndef fpc}
   TFPList = TList;
   TFPList = TList;
@@ -216,6 +216,7 @@ type
     function GetPrefix: DOMString; virtual;
     function GetPrefix: DOMString; virtual;
     procedure SetPrefix(const Value: DOMString); virtual;
     procedure SetPrefix(const Value: DOMString); virtual;
     function GetOwnerDocument: TDOMDocument; virtual;
     function GetOwnerDocument: TDOMDocument; virtual;
+    function GetBaseURI: DOMString;
     procedure SetReadOnly(Value: Boolean);
     procedure SetReadOnly(Value: Boolean);
     procedure Changing;
     procedure Changing;
   public
   public
@@ -255,11 +256,15 @@ type
     property Prefix: DOMString read GetPrefix write SetPrefix;
     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;
+    function LookupPrefix(const nsURI: DOMString): DOMString;
     function LookupNamespaceURI(const APrefix: DOMString): DOMString;
     function LookupNamespaceURI(const APrefix: DOMString): DOMString;
+    function IsDefaultNamespace(const nsURI: DOMString): Boolean;
+    property baseURI: DOMString read GetBaseURI;
     // Extensions to DOM interface:
     // Extensions to DOM interface:
     function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; virtual;
     function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; virtual;
     function FindNode(const ANodeName: DOMString): TDOMNode; virtual;
     function FindNode(const ANodeName: DOMString): TDOMNode; virtual;
     function CompareName(const name: DOMString): Integer; virtual;
     function CompareName(const name: DOMString): Integer; virtual;
+    property Flags: TNodeFlags read FFlags;
   end;
   end;
 
 
   TDOMNodeClass = class of TDOMNode;
   TDOMNodeClass = class of TDOMNode;
@@ -450,6 +455,8 @@ type
     function Alloc(AClass: TDOMNodeClass): TDOMNode;
     function Alloc(AClass: TDOMNodeClass): TDOMNode;
   public
   public
     function IndexOfNS(const nsURI: DOMString; AddIfAbsent: Boolean = False): Integer;
     function IndexOfNS(const nsURI: DOMString; AddIfAbsent: Boolean = False): Integer;
+    function InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode; override;
+    function ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode; override;
     property DocType: TDOMDocumentType read GetDocType;
     property DocType: TDOMDocumentType read GetDocType;
     property Impl: TDOMImplementation read FImplementation;
     property Impl: TDOMImplementation read FImplementation;
     property DocumentElement: TDOMElement read GetDocumentElement;
     property DocumentElement: TDOMElement read GetDocumentElement;
@@ -575,6 +582,7 @@ type
     function GetNodeType: Integer; override;
     function GetNodeType: Integer; override;
     function GetAttributes: TDOMNamedNodeMap; override;
     function GetAttributes: TDOMNamedNodeMap; override;
     procedure AttachDefaultAttrs;
     procedure AttachDefaultAttrs;
+    function InternalLookupPrefix(const nsURI: DOMString; Original: TDOMElement): DOMString;
     procedure RestoreDefaultAttr(AttrDef: TDOMAttr);
     procedure RestoreDefaultAttr(AttrDef: TDOMAttr);
   public
   public
     destructor Destroy; override;
     destructor Destroy; override;
@@ -1140,10 +1148,17 @@ function GetAncestorElement(n: TDOMNode): TDOMElement;
 var
 var
   parent: TDOMNode;
   parent: TDOMNode;
 begin
 begin
-  parent := n.ParentNode;
-  while Assigned(parent) and (parent.NodeType <> ELEMENT_NODE) do
-    parent := parent.ParentNode;
-  Result := TDOMElement(parent);
+  case n.nodeType of
+    DOCUMENT_NODE:
+      result := TDOMDocument(n).documentElement;
+    ATTRIBUTE_NODE:
+      result := TDOMAttr(n).OwnerElement;
+  else
+    parent := n.ParentNode;
+    while Assigned(parent) and (parent.NodeType <> ELEMENT_NODE) do
+      parent := parent.ParentNode;
+    Result := TDOMElement(parent);
+  end;  
 end;
 end;
 
 
 // TODO: specs prescribe to return default namespace if APrefix=null,
 // TODO: specs prescribe to return default namespace if APrefix=null,
@@ -1158,39 +1173,89 @@ begin
   Result := '';
   Result := '';
   if Self = nil then
   if Self = nil then
     Exit;
     Exit;
-  case NodeType of
-    ELEMENT_NODE:
+  if nodeType = ELEMENT_NODE then
+  begin
+    if (nfLevel2 in FFlags) and (TDOMElement(Self).Prefix = APrefix) then
+    begin
+      result := Self.NamespaceURI;
+      Exit;
+    end;
+    if HasAttributes then
     begin
     begin
-      if (nfLevel2 in FFlags) and (TDOMElement(Self).Prefix = APrefix) then
+      Map := Attributes;
+      for I := 0 to Map.Length-1 do
       begin
       begin
-        result := Self.NamespaceURI;
-        Exit;
-      end;
-      if HasAttributes then
+        Attr := TDOMAttr(Map[I]);
+        // should ignore level 1 atts here
+        if ((Attr.Prefix = 'xmlns') and (Attr.localName = APrefix)) or
+           ((Attr.localName = 'xmlns') and (APrefix = '')) then
+        begin
+          result := Attr.NodeValue;
+          Exit;
+        end;
+      end
+    end;
+  end;  
+  result := GetAncestorElement(Self).LookupNamespaceURI(APrefix);
+end;
+
+function TDOMNode.LookupPrefix(const nsURI: DOMString): DOMString;
+begin
+  Result := '';
+  if (nsURI = '') or (Self = nil) then
+    Exit;
+  if nodeType = ELEMENT_NODE then
+    result := TDOMElement(Self).InternalLookupPrefix(nsURI, TDOMElement(Self))
+  else
+    result := GetAncestorElement(Self).LookupPrefix(nsURI);
+end;
+
+function TDOMNode.IsDefaultNamespace(const nsURI: DOMString): Boolean;
+var
+  Attr: TDOMAttr;
+  Map: TDOMNamedNodeMap;
+  I: Integer;
+begin
+  Result := False;
+  if Self = nil then
+    Exit;
+  if nodeType = ELEMENT_NODE then
+  begin
+    if TDOMElement(Self).FNSI.PrefixLen = 0 then
+    begin
+      result := (nsURI = namespaceURI);
+      Exit;
+    end  
+    else if HasAttributes then
+    begin
+      Map := Attributes;
+      for I := 0 to Map.Length-1 do
       begin
       begin
-        Map := Attributes;
-        for I := 0 to Map.Length-1 do
+        Attr := TDOMAttr(Map[I]);
+        if Attr.LocalName = 'xmlns' then
         begin
         begin
-          Attr := TDOMAttr(Map[I]);
-          // should ignore level 1 atts here
-          if ((Attr.Prefix = 'xmlns') and (Attr.localName = APrefix)) or
-             ((Attr.localName = 'xmlns') and (APrefix = '')) then
-          begin
-            result := Attr.NodeValue;
-            Exit;
-          end;
-        end
+          result := (Attr.Value = nsURI);
+          Exit;
+        end;
       end;
       end;
-      result := GetAncestorElement(Self).LookupNamespaceURI(APrefix);
     end;
     end;
-    DOCUMENT_NODE:
-      result := TDOMDocument(Self).documentElement.LookupNamespaceURI(APrefix);
-
-    ATTRIBUTE_NODE:
-      result := TDOMAttr(Self).OwnerElement.LookupNamespaceURI(APrefix);
+  end;
+  result := GetAncestorElement(Self).IsDefaultNamespace(nsURI);
+end;
 
 
+function TDOMNode.GetBaseURI: DOMString;
+begin
+  case NodeType of
+  // !! Incomplete !!
+    DOCUMENT_NODE:
+      result := TDOMDocument(Self).FDocumentURI;
+    PROCESSING_INSTRUCTION_NODE:
+      if Assigned(ParentNode) then
+        result := ParentNode.GetBaseURI
+      else
+        result := OwnerDocument.DocumentURI;
   else
   else
-    Result := GetAncestorElement(Self).LookupNamespaceURI(APrefix);
+    result := '';
   end;
   end;
 end;
 end;
 
 
@@ -2167,6 +2232,32 @@ begin
   Result := nil;
   Result := nil;
 end;
 end;
 
 
+function TDOMDocument.InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode;
+var
+  nType: Integer;
+begin
+  nType := NewChild.NodeType;
+  if ((nType = ELEMENT_NODE) and Assigned(DocumentElement)) or
+     ((nType = DOCUMENT_TYPE_NODE) and Assigned(DocType)) then
+       raise EDOMHierarchyRequest.Create('Document.InsertBefore');
+  Result := inherited InsertBefore(NewChild, RefChild);
+end;
+
+function TDOMDocument.ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode;
+var
+  nType: Integer;
+begin
+  nType := NewChild.NodeType;
+  if ((nType = ELEMENT_NODE) and (OldChild = DocumentElement)) or   // root can be replaced by another element
+     ((nType = DOCUMENT_TYPE_NODE) and (OldChild = DocType)) then   // and so can be DTD
+  begin
+    inherited InsertBefore(NewChild, OldChild);
+    Result := RemoveChild(OldChild);
+  end
+  else
+    Result := inherited ReplaceChild(NewChild, OldChild);
+end;
+
 function TDOMDocument.GetDocumentElement: TDOMElement;
 function TDOMDocument.GetDocumentElement: TDOMElement;
 var
 var
   node: TDOMNode;
   node: TDOMNode;
@@ -2677,6 +2768,36 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TDOMElement.InternalLookupPrefix(const nsURI: DOMString; Original: TDOMElement): DOMString;
+var
+  I: Integer;
+  Attr: TDOMAttr;
+begin
+  result := '';
+  if Self = nil then
+    Exit;
+  if (nfLevel2 in FFlags) and (namespaceURI = nsURI) and (FNSI.PrefixLen > 0) then
+  begin
+    Result := Prefix;
+    if Original.LookupNamespaceURI(result) = nsURI then
+      Exit;
+  end;
+  if Assigned(FAttributes) then
+  begin
+    for I := 0 to FAttributes.Length-1 do
+    begin
+      Attr := TDOMAttr(FAttributes[I]);
+      if (Attr.Prefix = 'xmlns') and (Attr.Value = nsURI) then
+      begin
+        result := Attr.LocalName;
+        if Original.LookupNamespaceURI(result) = nsURI then
+          Exit;
+      end;
+    end;
+  end;
+  result := GetAncestorElement(Self).InternalLookupPrefix(nsURI, Original);
+end;
+
 procedure TDOMElement.RestoreDefaultAttr(AttrDef: TDOMAttr);
 procedure TDOMElement.RestoreDefaultAttr(AttrDef: TDOMAttr);
 var
 var
   Attr: TDOMAttr;
   Attr: TDOMAttr;
@@ -3218,7 +3339,7 @@ begin
   end
   end
   else
   else
   begin
   begin
-    if PAnsiChar(FCurrBlock) = PAnsiChar(FCurrExtent) + sizeof(TExtent) then
+    if PAnsiChar(FCurrBlock) < PAnsiChar(FCurrExtent) + sizeof(TExtent) then
       AddExtent(FCurrExtentSize * 2);
       AddExtent(FCurrExtentSize * 2);
     Result := FCurrBlock;
     Result := FCurrBlock;
     Dec(PAnsiChar(FCurrBlock), FElementSize);
     Dec(PAnsiChar(FCurrBlock), FElementSize);

+ 193 - 221
packages/fcl-xml/src/xmlread.pp

@@ -67,7 +67,10 @@ type
     FResolveExternals: Boolean;
     FResolveExternals: Boolean;
     FNamespaces: Boolean;
     FNamespaces: Boolean;
     FDisallowDoctype: Boolean;
     FDisallowDoctype: Boolean;
+    FCanonical: Boolean;
     FMaxChars: Cardinal;
     FMaxChars: Cardinal;
+    function GetCanonical: Boolean;
+    procedure SetCanonical(aValue: Boolean);
   public
   public
     property Validate: Boolean read FValidate write FValidate;
     property Validate: Boolean read FValidate write FValidate;
     property PreserveWhitespace: Boolean read FPreserveWhitespace write FPreserveWhitespace;
     property PreserveWhitespace: Boolean read FPreserveWhitespace write FPreserveWhitespace;
@@ -78,6 +81,7 @@ type
     property Namespaces: Boolean read FNamespaces write FNamespaces;
     property Namespaces: Boolean read FNamespaces write FNamespaces;
     property DisallowDoctype: Boolean read FDisallowDoctype write FDisallowDoctype;
     property DisallowDoctype: Boolean read FDisallowDoctype write FDisallowDoctype;
     property MaxChars: Cardinal read FMaxChars write FMaxChars;
     property MaxChars: Cardinal read FMaxChars write FMaxChars;
+    property CanonicalForm: Boolean read GetCanonical write SetCanonical;
   end;
   end;
 
 
   // NOTE: DOM 3 LS ACTION_TYPE enumeration starts at 1
   // NOTE: DOM 3 LS ACTION_TYPE enumeration starts at 1
@@ -94,7 +98,7 @@ type
   private
   private
     FStream: TStream;
     FStream: TStream;
     FStringData: string;
     FStringData: string;
-//    FBaseURI: WideString;
+    FBaseURI: WideString;
     FSystemID: WideString;
     FSystemID: WideString;
     FPublicID: WideString;
     FPublicID: WideString;
 //    FEncoding: string;
 //    FEncoding: string;
@@ -103,7 +107,7 @@ type
     constructor Create(const AStringData: string); overload;
     constructor Create(const AStringData: string); overload;
     property Stream: TStream read FStream;
     property Stream: TStream read FStream;
     property StringData: string read FStringData;
     property StringData: string read FStringData;
-//    property BaseURI: WideString read FBaseURI write FBaseURI;
+    property BaseURI: WideString read FBaseURI write FBaseURI;
     property SystemID: WideString read FSystemID write FSystemID;
     property SystemID: WideString read FSystemID write FSystemID;
     property PublicID: WideString read FPublicID write FPublicID;
     property PublicID: WideString read FPublicID write FPublicID;
 //    property Encoding: string read FEncoding write FEncoding;
 //    property Encoding: string read FEncoding write FEncoding;
@@ -191,10 +195,8 @@ type
     LFPos: PWideChar;
     LFPos: PWideChar;
     FXML11Rules: Boolean;
     FXML11Rules: Boolean;
     FSystemID: WideString;
     FSystemID: WideString;
-    FPublicID: WideString;
     FCharCount: Cardinal;
     FCharCount: Cardinal;
     function GetSystemID: WideString;
     function GetSystemID: WideString;
-    function GetPublicID: WideString;
   protected
   protected
     function Reload: Boolean; virtual;
     function Reload: Boolean; virtual;
   public
   public
@@ -208,7 +210,6 @@ type
     function SetEncoding(const AEncoding: string): Boolean; virtual;
     function SetEncoding(const AEncoding: string): Boolean; virtual;
     function Matches(const arg: WideString): Boolean;
     function Matches(const arg: WideString): Boolean;
     property SystemID: WideString read GetSystemID write FSystemID;
     property SystemID: WideString read GetSystemID write FSystemID;
-    property PublicID: WideString read GetPublicID write FPublicID;
   end;
   end;
 
 
   TXMLDecodingSource = class(TXMLCharSource)
   TXMLDecodingSource = class(TXMLCharSource)
@@ -307,14 +308,6 @@ type
 
 
   TCheckNameFlags = set of (cnOptional, cnToken);
   TCheckNameFlags = set of (cnOptional, cnToken);
   
   
-  TBinding = class
-  public
-    uri: WideString;
-    next: TBinding;
-    prevPrefixBinding: TObject;
-    Prefix: PHashItem;
-  end;
-
   TPrefixedAttr = record
   TPrefixedAttr = record
     Attr: TDOMAttr;
     Attr: TDOMAttr;
     PrefixLen: Integer;  // to avoid recalculation
     PrefixLen: Integer;  // to avoid recalculation
@@ -345,12 +338,10 @@ type
     FDTDStartPos: PWideChar;
     FDTDStartPos: PWideChar;
     FIntSubset: TWideCharBuf;
     FIntSubset: TWideCharBuf;
     FAttrTag: Cardinal;
     FAttrTag: Cardinal;
-    FPrefixes: THashTable;
-    FBindings: TFPList;
-    FDefaultPrefix: THashItem;
+    FOwnsDoctype: Boolean;
+
+    FNSHelper: TNSSupport;
     FWorkAtts: array of TPrefixedAttr;
     FWorkAtts: array of TPrefixedAttr;
-    FBindingStack: array of TBinding;
-    FFreeBindings: TBinding;
     FNsAttHash: TDblHashArray;
     FNsAttHash: TDblHashArray;
     FStdPrefix_xml: PHashItem;
     FStdPrefix_xml: PHashItem;
     FStdPrefix_xmlns: PHashItem;
     FStdPrefix_xmlns: PHashItem;
@@ -364,9 +355,10 @@ type
     FResolveExternals: Boolean;
     FResolveExternals: Boolean;
     FNamespaces: Boolean;
     FNamespaces: Boolean;
     FDisallowDoctype: Boolean;
     FDisallowDoctype: Boolean;
+    FCanonical: Boolean;
     FMaxChars: Cardinal;
     FMaxChars: Cardinal;
 
 
-    procedure RaiseExpectedQmark;
+    procedure SkipQuote(out Delim: WideChar; required: Boolean = True);
     procedure Initialize(ASource: TXMLCharSource);
     procedure Initialize(ASource: TXMLCharSource);
     function DoParseAttValue(Delim: WideChar): Boolean;
     function DoParseAttValue(Delim: WideChar): Boolean;
     function ContextPush(AEntity: TDOMEntityEx): Boolean;
     function ContextPush(AEntity: TDOMEntityEx): Boolean;
@@ -382,7 +374,7 @@ type
     procedure StandaloneError(LineOffs: Integer = 0);
     procedure StandaloneError(LineOffs: Integer = 0);
     procedure CallErrorHandler(E: EXMLReadError);
     procedure CallErrorHandler(E: EXMLReadError);
     function  FindOrCreateElDef: TDOMElementDef;
     function  FindOrCreateElDef: TDOMElementDef;
-    function  SkipUntilSeq(const Delim: TSetOfChar; const More: array of WideChar): Boolean;
+    function  SkipUntilSeq(const Delim: TSetOfChar; c1: WideChar; c2: WideChar = #0): Boolean;
     procedure CheckMaxChars;
     procedure CheckMaxChars;
   protected
   protected
     FCursor: TDOMNode_WithChildren;
     FCursor: TDOMNode_WithChildren;
@@ -408,7 +400,6 @@ type
     function  ExpectName: WideString;                                   // [5]
     function  ExpectName: WideString;                                   // [5]
     procedure SkipQuotedLiteral(out Literal: WideString; required: Boolean = True);
     procedure SkipQuotedLiteral(out Literal: WideString; required: Boolean = True);
     procedure ExpectAttValue;                                           // [10]
     procedure ExpectAttValue;                                           // [10]
-    procedure SkipPubidLiteral(out Literal: WideString);                // [12]
     procedure ParseComment;                                             // [15]
     procedure ParseComment;                                             // [15]
     procedure ParsePI;                                                  // [16]
     procedure ParsePI;                                                  // [16]
     procedure ParseCDSect;                                              // [18]
     procedure ParseCDSect;                                              // [18]
@@ -435,11 +426,10 @@ type
     procedure ExpectChoiceOrSeq(CP: TContentParticle);
     procedure ExpectChoiceOrSeq(CP: TContentParticle);
     procedure ParseElementDecl;
     procedure ParseElementDecl;
     procedure ParseNotationDecl;
     procedure ParseNotationDecl;
-    function ResolveEntity(const AbsSysID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
+    function ResolveEntity(const SystemID, PublicID, BaseURI: WideString; out Source: TXMLCharSource): Boolean;
     procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap);
     procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap);
     procedure ProcessNamespaceAtts(Element: TDOMElement);
     procedure ProcessNamespaceAtts(Element: TDOMElement);
-    procedure AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding);
-    procedure EndNamespaceScope(var Chain: TBinding);
+    procedure AddBinding(Attr: TDOMAttr; PrefixPtr: PWideChar; PrefixLen: Integer);
 
 
     procedure PushVC(aElement: TDOMElement; aElDef: TDOMElementDef);
     procedure PushVC(aElement: TDOMElement; aElDef: TDOMElementDef);
     procedure PopVC;
     procedure PopVC;
@@ -696,6 +686,30 @@ begin
     CompareMem(ABuf.Buffer, Pointer(Arg), ABuf.Length*sizeof(WideChar));
     CompareMem(ABuf.Buffer, Pointer(Arg), ABuf.Length*sizeof(WideChar));
 end;
 end;
 
 
+{ TDOMParseOptions }
+
+function TDOMParseOptions.GetCanonical: Boolean;
+begin
+  Result := FCanonical and FExpandEntities and FCDSectionsAsText and
+  { (not normalizeCharacters) and } FNamespaces and
+  { namespaceDeclarations and } FPreserveWhitespace;
+end;
+
+procedure TDOMParseOptions.SetCanonical(aValue: Boolean);
+begin
+  FCanonical := aValue;
+  if aValue then
+  begin
+    FExpandEntities := True;
+    FCDSectionsAsText := True;
+    FNamespaces := True;
+    FPreserveWhitespace := True;
+    { normalizeCharacters := False; }
+    { namespaceDeclarations := True; }
+    { wellFormed := True; }
+  end;
+end;
+
 { TXMLInputSource }
 { TXMLInputSource }
 
 
 constructor TXMLInputSource.Create(AStream: TStream);
 constructor TXMLInputSource.Create(AStream: TStream);
@@ -744,7 +758,7 @@ begin
   ADoc := nil;
   ADoc := nil;
   with TXMLReader.Create(Self) do
   with TXMLReader.Create(Self) do
   try
   try
-    if ResolveEntity(URI, '', Src) then
+    if ResolveEntity(URI, '', '', Src) then
       ProcessXML(Src)
       ProcessXML(Src)
     else
     else
       DoErrorPos(esFatal, 'The specified URI could not be resolved', NullLocation);
       DoErrorPos(esFatal, 'The specified URI could not be resolved', NullLocation);
@@ -819,16 +833,6 @@ begin
   Result := True; // always succeed
   Result := True; // always succeed
 end;
 end;
 
 
-function TXMLCharSource.GetPublicID: WideString;
-begin
-  if FPublicID <> '' then
-    Result := FPublicID
-  else if Assigned(FParent) then
-    Result := FParent.PublicID
-  else
-    Result := '';
-end;
-
 function TXMLCharSource.GetSystemID: WideString;
 function TXMLCharSource.GetSystemID: WideString;
 begin
 begin
   if FSystemID <> '' then
   if FSystemID <> '' then
@@ -879,7 +883,11 @@ begin
   if (FBufEnd >= FBuf + Length(arg)) or Reload then
   if (FBufEnd >= FBuf + Length(arg)) or Reload then
     Result := CompareMem(Pointer(arg), FBuf, Length(arg)*sizeof(WideChar));
     Result := CompareMem(Pointer(arg), FBuf, Length(arg)*sizeof(WideChar));
   if Result then
   if Result then
+  begin
     Inc(FBuf, Length(arg));
     Inc(FBuf, Length(arg));
+    if FBuf >= FBufEnd then
+      Reload;
+  end;
 end;
 end;
 
 
 { TXMLDecodingSource }
 { TXMLDecodingSource }
@@ -1164,7 +1172,7 @@ begin
     else if SrcIn.FStringData <> '' then
     else if SrcIn.FStringData <> '' then
       SrcOut := TXMLStreamInputSource.Create(TStringStream.Create(SrcIn.FStringData), True)
       SrcOut := TXMLStreamInputSource.Create(TStringStream.Create(SrcIn.FStringData), True)
     else if (SrcIn.SystemID <> '') then
     else if (SrcIn.SystemID <> '') then
-      ResolveEntity(SrcIn.SystemID, SrcIn.PublicID, SrcOut);
+      ResolveEntity(SrcIn.SystemID, SrcIn.PublicID, SrcIn.BaseURI, SrcOut);
   end;
   end;
   if (SrcOut = nil) and (FSource = nil) then
   if (SrcOut = nil) and (FSource = nil) then
     DoErrorPos(esFatal, 'No input source specified', NullLocation);
     DoErrorPos(esFatal, 'No input source specified', NullLocation);
@@ -1176,14 +1184,17 @@ begin
   Loc.LinePos := FSource.FBuf-FSource.LFPos;
   Loc.LinePos := FSource.FBuf-FSource.LFPos;
 end;
 end;
 
 
-function TXMLReader.ResolveEntity(const AbsSysID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
+function TXMLReader.ResolveEntity(const SystemID, PublicID, BaseURI: WideString; out Source: TXMLCharSource): Boolean;
 var
 var
+  AbsSysID: WideString;
   Filename: string;
   Filename: string;
   Stream: TStream;
   Stream: TStream;
   fd: THandle;
   fd: THandle;
 begin
 begin
   Source := nil;
   Source := nil;
   Result := False;
   Result := False;
+  if not ResolveRelativeURI(BaseURI, SystemID, AbsSysID) then
+    Exit;
   { TODO: alternative resolvers
   { TODO: alternative resolvers
     These may be 'internal' resolvers or a handler set by application.
     These may be 'internal' resolvers or a handler set by application.
     Internal resolvers should probably produce a TStream
     Internal resolvers should probably produce a TStream
@@ -1199,7 +1210,6 @@ begin
       Stream := THandleOwnerStream.Create(fd);
       Stream := THandleOwnerStream.Create(fd);
       Source := TXMLStreamInputSource.Create(Stream, True);
       Source := TXMLStreamInputSource.Create(Stream, True);
       Source.SystemID := AbsSysID;    // <- Revisit: Really need absolute sysID?
       Source.SystemID := AbsSysID;    // <- Revisit: Really need absolute sysID?
-      Source.PublicID := PublicID;
     end;
     end;
   end;
   end;
   Result := Assigned(Source);
   Result := Assigned(Source);
@@ -1213,11 +1223,6 @@ begin
   FSource.Initialize;
   FSource.Initialize;
 end;
 end;
 
 
-procedure TXMLReader.RaiseExpectedQmark;
-begin
-  FatalError('Expected single or double quote');
-end;
-
 procedure TXMLReader.FatalError(Expected: WideChar);
 procedure TXMLReader.FatalError(Expected: WideChar);
 begin
 begin
 // FIX: don't output what is found - anything may be found, including exploits...
 // FIX: don't output what is found - anything may be found, including exploits...
@@ -1258,9 +1263,15 @@ end;
 procedure TXMLReader.DoErrorPos(Severity: TErrorSeverity; const descr: string; const ErrPos: TLocation);
 procedure TXMLReader.DoErrorPos(Severity: TErrorSeverity; const descr: string; const ErrPos: TLocation);
 var
 var
   E: EXMLReadError;
   E: EXMLReadError;
+  sysid: WideString;
 begin
 begin
   if Assigned(FSource) then
   if Assigned(FSource) then
-    E := EXMLReadError.CreateFmt('In ''%s'' (line %d pos %d): %s', [FSource.SystemID, ErrPos.Line, ErrPos.LinePos, descr])
+  begin
+    sysid := FSource.FSystemID;
+    if (sysid = '') and Assigned(FSource.FEntity) then
+      sysid := TDOMEntityEx(FSource.FEntity).FURI;
+    E := EXMLReadError.CreateFmt('In ''%s'' (line %d pos %d): %s', [sysid, ErrPos.Line, ErrPos.LinePos, descr]);
+  end
   else
   else
     E := EXMLReadError.Create(descr);
     E := EXMLReadError.Create(descr);
   E.FSeverity := Severity;
   E.FSeverity := Severity;
@@ -1393,12 +1404,22 @@ begin
   end;  
   end;  
 end;
 end;
 
 
+procedure TXMLReader.SkipQuote(out Delim: WideChar; required: Boolean);
+begin
+  Delim := #0;
+  if (FSource.FBuf^ = '''') or (FSource.FBuf^ = '"') then
+  begin
+    Delim := FSource.FBuf^;
+    FSource.NextChar;  // skip quote
+  end
+  else if required then
+    FatalError('Expected single or double quote');
+end;
+
 const
 const
   PrefixDefault: array[0..4] of WideChar = ('x','m','l','n','s');
   PrefixDefault: array[0..4] of WideChar = ('x','m','l','n','s');
 
 
 constructor TXMLReader.Create;
 constructor TXMLReader.Create;
-var
-  b: TBinding;
 begin
 begin
   inherited Create;
   inherited Create;
   BufAllocate(FName, 128);
   BufAllocate(FName, 128);
@@ -1406,20 +1427,12 @@ begin
   FIDRefs := TFPList.Create;
   FIDRefs := TFPList.Create;
   FNotationRefs := TFPList.Create;
   FNotationRefs := TFPList.Create;
 
 
-  FPrefixes := THashTable.Create(16, False);
-  FBindings := TFPList.Create;
+  FNSHelper := TNSSupport.Create;
+
   FNsAttHash := TDblHashArray.Create;
   FNsAttHash := TDblHashArray.Create;
   SetLength(FWorkAtts, 16);
   SetLength(FWorkAtts, 16);
-  SetLength(FBindingStack, 16);
-  FStdPrefix_xml := FPrefixes.FindOrAdd(@PrefixDefault, 3);
-  FStdPrefix_xmlns := FPrefixes.FindOrAdd(@PrefixDefault, 5);
-  { implicit binding for the 'xml' prefix }
-  b := TBinding.Create;
-  FBindings.Add(b);
-  FStdPrefix_xml^.Data := b;
-  b.uri := stduri_xml;
-  b.Prefix := FStdPrefix_xml;
-
+  FStdPrefix_xml := FNSHelper.GetPrefix(@PrefixDefault, 3);
+  FStdPrefix_xmlns := FNSHelper.GetPrefix(@PrefixDefault, 5);
   // Set char rules to XML 1.0
   // Set char rules to XML 1.0
   FNamePages := @NamePages;
   FNamePages := @NamePages;
   SetLength(FValidator, 16);
   SetLength(FValidator, 16);
@@ -1437,12 +1450,11 @@ begin
   FResolveExternals := FCtrl.Options.ResolveExternals;
   FResolveExternals := FCtrl.Options.ResolveExternals;
   FNamespaces := FCtrl.Options.Namespaces;
   FNamespaces := FCtrl.Options.Namespaces;
   FDisallowDoctype := FCtrl.Options.DisallowDoctype;
   FDisallowDoctype := FCtrl.Options.DisallowDoctype;
+  FCanonical := FCtrl.Options.CanonicalForm;
   FMaxChars := FCtrl.Options.MaxChars;
   FMaxChars := FCtrl.Options.MaxChars;
 end;
 end;
 
 
 destructor TXMLReader.Destroy;
 destructor TXMLReader.Destroy;
-var
-  I: Integer;
 begin
 begin
   if Assigned(FEntityValue.Buffer) then
   if Assigned(FEntityValue.Buffer) then
     FreeMem(FEntityValue.Buffer);
     FreeMem(FEntityValue.Buffer);
@@ -1455,10 +1467,10 @@ begin
   ClearRefs(FNotationRefs);
   ClearRefs(FNotationRefs);
   ClearRefs(FIDRefs);
   ClearRefs(FIDRefs);
   FNsAttHash.Free;
   FNsAttHash.Free;
-  for I := FBindings.Count-1 downto 0 do
-    TObject(FBindings.List^[I]).Free;
-  FPrefixes.Free;
-  FBindings.Free;
+  FNSHelper.Free;
+  if FOwnsDoctype then
+    FDocType.Free;
+
   FNotationRefs.Free;
   FNotationRefs.Free;
   FIDRefs.Free;
   FIDRefs.Free;
   inherited Destroy;
   inherited Destroy;
@@ -1732,7 +1744,7 @@ var
 begin
 begin
   if (AEntity.SystemID <> '') and not AEntity.FResolved then
   if (AEntity.SystemID <> '') and not AEntity.FResolved then
   begin
   begin
-    Result := ResolveEntity(AEntity.FURI, AEntity.PublicID, Src);
+    Result := ResolveEntity(AEntity.SystemID, AEntity.PublicID, AEntity.FURI, Src);
     if not Result then
     if not Result then
     begin
     begin
       // TODO: a detailed message like SysErrorMessage(GetLastError) would be great here 
       // TODO: a detailed message like SysErrorMessage(GetLastError) would be great here 
@@ -1743,10 +1755,11 @@ begin
   else
   else
   begin
   begin
     Src := TXMLCharSource.Create(AEntity.FReplacementText);
     Src := TXMLCharSource.Create(AEntity.FReplacementText);
-    // needed in case of prefetched external PE
-    Src.SystemID := AEntity.FURI;
     Src.FLineNo := AEntity.FStartLocation.Line;
     Src.FLineNo := AEntity.FStartLocation.Line;
     Src.LFPos := Src.FBuf - AEntity.FStartLocation.LinePos;
     Src.LFPos := Src.FBuf - AEntity.FStartLocation.LinePos;
+    // needed in case of prefetched external PE
+    if AEntity.SystemID <> '' then
+      Src.SystemID := AEntity.FURI;
   end;
   end;
 
 
   AEntity.FOnStack := True;
   AEntity.FOnStack := True;
@@ -1884,6 +1897,7 @@ begin
       PEnt.FCharCount := FValue.Length;
       PEnt.FCharCount := FValue.Length;
       PEnt.FStartLocation.Line := 1;
       PEnt.FStartLocation.Line := 1;
       PEnt.FStartLocation.LinePos := 1;
       PEnt.FStartLocation.LinePos := 1;
+      PEnt.FURI := FSource.SystemID;    // replace base URI with absolute one
     finally
     finally
       ContextPop;
       ContextPop;
       PEnt.FResolved := True;
       PEnt.FResolved := True;
@@ -1903,10 +1917,7 @@ procedure TXMLReader.ExpectAttValue;    // [10]
 var
 var
   Delim: WideChar;
   Delim: WideChar;
 begin
 begin
-  if (FSource.FBuf^ <> '''') and (FSource.FBuf^ <> '"') then
-    RaiseExpectedQmark;
-  Delim := FSource.FBuf^;
-  FSource.NextChar;  // skip quote
+  SkipQuote(Delim);
   StoreLocation(FTokenStart);
   StoreLocation(FTokenStart);
   if not DoParseAttValue(Delim) then
   if not DoParseAttValue(Delim) then
     FatalError('Literal has no closing quote',-1);
     FatalError('Literal has no closing quote',-1);
@@ -1916,10 +1927,9 @@ procedure TXMLReader.SkipQuotedLiteral(out Literal: WideString; required: Boolea
 var
 var
   Delim: WideChar;
   Delim: WideChar;
 begin
 begin
-  if (FSource.FBuf^ = '''') or (FSource.FBuf^ = '"') then
+  SkipQuote(Delim, required);
+  if Delim <> #0 then
   begin
   begin
-    Delim := FSource.FBuf^;
-    FSource.NextChar;  // skip quote
     StoreLocation(FTokenStart);
     StoreLocation(FTokenStart);
     FValue.Length := 0;
     FValue.Length := 0;
     if Delim = '''' then
     if Delim = '''' then
@@ -1930,32 +1940,12 @@ begin
       FatalError('Literal has no closing quote', -1);
       FatalError('Literal has no closing quote', -1);
     FSource.NextChar;
     FSource.NextChar;
     SetString(Literal, FValue.Buffer, FValue.Length);
     SetString(Literal, FValue.Buffer, FValue.Length);
-  end
-  else if required then
-    RaiseExpectedQMark;
-end;
-
-procedure TXMLReader.SkipPubidLiteral(out Literal: WideString);         // [12]
-var
-  I: Integer;
-  wc: WideChar;
-begin
-  SkipQuotedLiteral(Literal);
-  for I := 1 to Length(Literal) do
-  begin
-    wc := Literal[I];
-    if (wc > #255) or not (Char(ord(wc)) in PubidChars) then
-      FatalError('Illegal Public ID literal', -1);
-    if (wc = #10) or (wc = #13) then
-      Literal[I] := #32;
   end;
   end;
 end;
 end;
 
 
-function TXMLReader.SkipUntilSeq(const Delim: TSetOfChar; const More: array of WideChar): Boolean;
+function TXMLReader.SkipUntilSeq(const Delim: TSetOfChar; c1: WideChar; c2: WideChar = #0): Boolean;
 var
 var
-  I: Integer;
   wc: WideChar;
   wc: WideChar;
-  Match: Boolean;
 begin
 begin
   Result := False;
   Result := False;
   FValue.Length := 0;
   FValue.Length := 0;
@@ -1965,18 +1955,12 @@ begin
     if wc <> #0 then
     if wc <> #0 then
     begin
     begin
       FSource.NextChar;
       FSource.NextChar;
-      if (FValue.Length > High(More)) then
+      if (FValue.Length > ord(c2 <> #0)) then
       begin
       begin
-        Match := True;
-        for I := High(More) downto Low(More) do
-          if FValue.Buffer[FValue.Length - (High(More)+1) + I] <> More[I] then
-          begin
-            Match := False;
-            Break;
-          end;
-        if Match then
+        if (FValue.Buffer[FValue.Length-1] = c1) and
+          ((c2 = #0) or ((c2 <> #0) and (FValue.Buffer[FValue.Length-2] = c2))) then
         begin
         begin
-          Dec(FValue.Length, High(More)+1);
+          Dec(FValue.Length, ord(c2 <> #0) + 1);
           Result := True;
           Result := True;
           Exit;
           Exit;
         end;
         end;
@@ -1986,15 +1970,10 @@ begin
   until wc = #0;
   until wc = #0;
 end;
 end;
 
 
-const
-  CommentEnd: array[0..0] of WideChar = ('-');
-  PIEnd:      array[0..0] of WideChar = ('?');
-  CDEnd:      array[0..1] of WideChar = (']',']');
-
 procedure TXMLReader.ParseComment;    // [15]
 procedure TXMLReader.ParseComment;    // [15]
 begin
 begin
   ExpectString('--');
   ExpectString('--');
-  if SkipUntilSeq([#0, '-'], CommentEnd) then
+  if SkipUntilSeq([#0, '-'], '-') then
   begin
   begin
     ExpectChar('>');
     ExpectChar('>');
     DoComment(FValue.Buffer, FValue.Length);
     DoComment(FValue.Buffer, FValue.Length);
@@ -2026,7 +2005,7 @@ begin
   if FSource.FBuf^ <> '?' then
   if FSource.FBuf^ <> '?' then
     SkipS(True);
     SkipS(True);
 
 
-  if SkipUntilSeq(GT_Delim, PIEnd) then
+  if SkipUntilSeq(GT_Delim, '?') then
   begin
   begin
     SetString(Value, FValue.Buffer, FValue.Length);
     SetString(Value, FValue.Buffer, FValue.Length);
     // SAX: ContentHandler.ProcessingInstruction(Name, Value);
     // SAX: ContentHandler.ProcessingInstruction(Name, Value);
@@ -2043,75 +2022,102 @@ begin
     FatalError('Unterminated processing instruction', -1);
     FatalError('Unterminated processing instruction', -1);
 end;
 end;
 
 
+const
+  verStr: array[Boolean] of WideString = ('1.0', '1.1');
+
 procedure TXMLReader.ParseXmlOrTextDecl(TextDecl: Boolean);
 procedure TXMLReader.ParseXmlOrTextDecl(TextDecl: Boolean);
 var
 var
   TmpStr: WideString;
   TmpStr: WideString;
   IsXML11: Boolean;
   IsXML11: Boolean;
+  Delim: WideChar;
+  buf: array[0..31] of WideChar;
+  I: Integer;
 begin
 begin
   SkipS(True);
   SkipS(True);
-  // VersionInfo: optional in TextDecl, required in XmlDecl
+  // [24] VersionInfo: optional in TextDecl, required in XmlDecl
   if (not TextDecl) or (FSource.FBuf^ = 'v') then
   if (not TextDecl) or (FSource.FBuf^ = 'v') then
   begin
   begin
-    ExpectString('version');                              // [24]
+    ExpectString('version');
     ExpectEq;
     ExpectEq;
-    SkipQuotedLiteral(TmpStr);
-    IsXML11 := False;
-    if TmpStr = '1.1' then     // Checking for bad chars is implied
-      IsXML11 := True
-    else if TmpStr <> '1.0' then
-    { should be no whitespace in these literals, but that isn't checked now }
+    SkipQuote(Delim);
+    StoreLocation(FTokenStart);
+    I := 0;
+    while (I < 3) and (FSource.FBuf^ <> Delim) do
+    begin
+      buf[I] := FSource.FBuf^;
+      Inc(I);
+      FSource.NextChar;
+    end;
+    if (I <> 3) or (buf[0] <> '1') or (buf[1] <> '.') or
+      ((buf[2] <> '0') and (buf[2] <> '1')) then
       FatalError('Illegal version number', -1);
       FatalError('Illegal version number', -1);
 
 
+    ExpectChar(Delim);
+    IsXML11 := buf[2] = '1';
+
     if not TextDecl then
     if not TextDecl then
     begin
     begin
       if doc.InheritsFrom(TXMLDocument) then
       if doc.InheritsFrom(TXMLDocument) then
-        TXMLDocument(doc).XMLVersion := TmpStr;
-      if IsXML11 then
-        XML11_BuildTables;
+        TXMLDocument(doc).XMLVersion := verStr[IsXML11];  // buf[0..2] works with FPC only
     end
     end
     else   // parsing external entity
     else   // parsing external entity
       if IsXML11 and not FXML11 then
       if IsXML11 and not FXML11 then
         FatalError('XML 1.0 document cannot invoke XML 1.1 entities', -1);
         FatalError('XML 1.0 document cannot invoke XML 1.1 entities', -1);
 
 
-    if FSource.FBuf^ <> '?' then
+    if TextDecl or (FSource.FBuf^ <> '?') then
       SkipS(True);
       SkipS(True);
   end;
   end;
 
 
-  // EncodingDecl: required in TextDecl, optional in XmlDecl
-  if TextDecl or (FSource.FBuf^ = 'e') then                    // [80]
+  // [80] EncodingDecl: required in TextDecl, optional in XmlDecl
+  if TextDecl or (FSource.FBuf^ = 'e') then
   begin
   begin
     ExpectString('encoding');
     ExpectString('encoding');
     ExpectEq;
     ExpectEq;
-    SkipQuotedLiteral(TmpStr);
-
-    if not IsValidXmlEncoding(TmpStr) then
-      FatalError('Illegal encoding name', -1);
+    SkipQuote(Delim);
+    I := 0;
+    while (I < 30) and (FSource.FBuf^ <> Delim) and (FSource.FBuf^ < #127) and
+      ((Char(ord(FSource.FBuf^)) in ['A'..'Z', 'a'..'z']) or
+      ((I > 0) and (Char(ord(FSource.FBuf^)) in ['0'..'9', '.', '-', '_']))) do
+    begin
+      buf[I] := FSource.FBuf^;
+      Inc(I);
+      FSource.NextChar;
+    end;
+    if not CheckForChar(Delim) then
+      FatalError('Illegal encoding name', i);
 
 
+    SetString(TmpStr, buf, i);
     if not FSource.SetEncoding(TmpStr) then  // <-- Wide2Ansi conversion here
     if not FSource.SetEncoding(TmpStr) then  // <-- Wide2Ansi conversion here
-      FatalError('Encoding ''%s'' is not supported', [TmpStr], -1);
+      FatalError('Encoding ''%s'' is not supported', [TmpStr], i+1);
     // getting here means that specified encoding is supported
     // getting here means that specified encoding is supported
     // TODO: maybe assign the 'preferred' encoding name?
     // TODO: maybe assign the 'preferred' encoding name?
     if not TextDecl and doc.InheritsFrom(TXMLDocument) then
     if not TextDecl and doc.InheritsFrom(TXMLDocument) then
       TXMLDocument(doc).Encoding := TmpStr;
       TXMLDocument(doc).Encoding := TmpStr;
 
 
     if FSource.FBuf^ <> '?' then
     if FSource.FBuf^ <> '?' then
-      SkipS(True);
+      SkipS(not TextDecl);
   end;
   end;
 
 
-  // SDDecl: forbidden in TextDecl, optional in XmlDecl
+  // [32] SDDecl: forbidden in TextDecl, optional in XmlDecl
   if (not TextDecl) and (FSource.FBuf^ = 's') then
   if (not TextDecl) and (FSource.FBuf^ = 's') then
   begin
   begin
     ExpectString('standalone');
     ExpectString('standalone');
     ExpectEq;
     ExpectEq;
-    SkipQuotedLiteral(TmpStr);
-    if TmpStr = 'yes' then
+    SkipQuote(Delim);
+    StoreLocation(FTokenStart);
+    if FSource.Matches('yes') then
       FStandalone := True
       FStandalone := True
-    else if TmpStr <> 'no' then
+    else if not FSource.Matches('no') then
       FatalError('Only "yes" or "no" are permitted as values of "standalone"', -1);
       FatalError('Only "yes" or "no" are permitted as values of "standalone"', -1);
+    ExpectChar(Delim);
     SkipS;
     SkipS;
   end;
   end;
 
 
   ExpectString('?>');
   ExpectString('?>');
+  { Switch to 1.1 rules only after declaration is parsed completely. This is to
+    ensure that NEL and LSEP within declaration are rejected (rmt-056, rmt-057) }
+  if (not TextDecl) and IsXML11 then
+    XML11_BuildTables;
 end;
 end;
 
 
 procedure TXMLReader.DTDReloadHook;
 procedure TXMLReader.DTDReloadHook;
@@ -2136,7 +2142,6 @@ end;
 procedure TXMLReader.ParseDoctypeDecl;    // [28]
 procedure TXMLReader.ParseDoctypeDecl;    // [28]
 var
 var
   Src: TXMLCharSource;
   Src: TXMLCharSource;
-  DoctypeURI: WideString;
 begin
 begin
   if FState >= rsDTD then
   if FState >= rsDTD then
     FatalError('Markup declaration is not allowed here');
     FatalError('Markup declaration is not allowed here');
@@ -2155,7 +2160,10 @@ begin
     SkipS;
     SkipS;
   finally
   finally
     // DONE: append node after its name has been set; always append to avoid leak
     // DONE: append node after its name has been set; always append to avoid leak
-    Doc.AppendChild(FDocType);
+    if FCanonical then
+      FOwnsDoctype := True
+    else
+      Doc.AppendChild(FDocType);
     FCursor := nil;
     FCursor := nil;
   end;
   end;
 
 
@@ -2179,8 +2187,7 @@ begin
 
 
   if (FDocType.SystemID <> '') then
   if (FDocType.SystemID <> '') then
   begin
   begin
-    ResolveRelativeURI(FSource.SystemID, FDocType.SystemID, DoctypeURI);
-    if ResolveEntity(DocTypeURI, FDocType.PublicID, Src) then
+    if ResolveEntity(FDocType.SystemID, FDocType.PublicID, FSource.SystemID, Src) then
     begin
     begin
       Initialize(Src);
       Initialize(Src);
       try
       try
@@ -2606,6 +2613,8 @@ begin
     CheckNCName;
     CheckNCName;
     ExpectWhitespace;
     ExpectWhitespace;
 
 
+    // remember where the entity is declared
+    Entity.FURI := FSource.SystemID;
     if (FSource.FBuf^ = '"') or (FSource.FBuf^ = '''') then
     if (FSource.FBuf^ = '"') or (FSource.FBuf^ = '''') then
     begin
     begin
       NDataAllowed := False;
       NDataAllowed := False;
@@ -2617,14 +2626,8 @@ begin
       SetString(Entity.FReplacementText, FEntityValue.Buffer, FEntityValue.Length);
       SetString(Entity.FReplacementText, FEntityValue.Buffer, FEntityValue.Length);
       Entity.FCharCount := FEntityValue.Length;
       Entity.FCharCount := FEntityValue.Length;
     end
     end
-    else
-    begin
-      if not ParseExternalID(Entity.FSystemID, Entity.FPublicID, False) then
-        FatalError('Expected entity value or external ID');
-      { need to resolve entity's SystemID relative to the current source,
-        which may differ from the source at the point of inclusion }
-      ResolveRelativeURI(FSource.SystemID, Entity.SystemID, Entity.FURI);
-    end;
+    else if not ParseExternalID(Entity.FSystemID, Entity.FPublicID, False) then
+      FatalError('Expected entity value or external ID');
 
 
     if NDataAllowed then                // [76]
     if NDataAllowed then                // [76]
     begin
     begin
@@ -2726,11 +2729,10 @@ begin
             else if FSource.Matches(']]>') then
             else if FSource.Matches(']]>') then
               Dec(IgnoreLevel)
               Dec(IgnoreLevel)
             else if wc <> #0 then
             else if wc <> #0 then
-              FSource.NextChar;
-          until (IgnoreLevel=0) or (wc = #0);
-// Since PE's are not recognized in ignore sections, reaching EOF is fatal.
-          if wc = #0 then
-            Break;
+              FSource.NextChar
+            else // PE's aren't recognized in ignore section, cannot ContextPop()
+              DoErrorPos(esFatal, 'IGNORE section is not closed', IgnoreLoc);
+          until IgnoreLevel=0;
         end;
         end;
       end
       end
       else
       else
@@ -2759,14 +2761,8 @@ begin
     end;
     end;
   until False;
   until False;
   FRecognizePE := False;
   FRecognizePE := False;
-  if (IncludeLevel > 0) or (IgnoreLevel > 0) then
-  begin
-    if IncludeLevel > 0 then
-      FTokenStart := IncludeLoc
-    else
-      FTokenStart := IgnoreLoc;
-    FatalError('Conditional section is not closed', -1);
-  end;
+  if IncludeLevel > 0 then
+    DoErrorPos(esFatal, 'INCLUDE section is not closed', IncludeLoc);
   if (FSource.DTDSubsetType = dsInternal) and (FSource.FBuf^ = ']') then
   if (FSource.DTDSubsetType = dsInternal) and (FSource.FBuf^ = ']') then
     Exit;
     Exit;
   if FSource.FBuf^ <> #0 then
   if FSource.FBuf^ <> #0 then
@@ -2789,7 +2785,7 @@ begin
   ExpectString('[CDATA[');
   ExpectString('[CDATA[');
   if FState <> rsRoot then
   if FState <> rsRoot then
     FatalError('Illegal at document level');
     FatalError('Illegal at document level');
-  if SkipUntilSeq(GT_Delim, CDEnd) then
+  if SkipUntilSeq(GT_Delim, ']', ']') then
     DoCDSect(FValue.Buffer, FValue.Length)
     DoCDSect(FValue.Buffer, FValue.Length)
   else
   else
     FatalError('Unterminated CDATA section', -1);
     FatalError('Unterminated CDATA section', -1);
@@ -3025,7 +3021,7 @@ begin
     ValidationError('Element ''%s'' is missing required sub-elements', [NewElem.NSI.QName^.Key], ErrOffset);
     ValidationError('Element ''%s'' is missing required sub-elements', [NewElem.NSI.QName^.Key], ErrOffset);
 
 
   if FNamespaces then
   if FNamespaces then
-    EndNamespaceScope(FBindingStack[FNesting]);
+    FNSHelper.EndElement;
   PopVC;
   PopVC;
 end;
 end;
 
 
@@ -3173,12 +3169,13 @@ begin
 end;
 end;
 
 
 
 
-procedure TXMLReader.AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding);
+procedure TXMLReader.AddBinding(Attr: TDOMAttr; PrefixPtr: PWideChar; PrefixLen: Integer);
 var
 var
   nsUri: DOMString;
   nsUri: DOMString;
-  b: TBinding;
+  Prefix: PHashItem;
 begin
 begin
   nsUri := Attr.NodeValue;
   nsUri := Attr.NodeValue;
+  Prefix := FNSHelper.GetPrefix(PrefixPtr, PrefixLen);
   { 'xml' is allowed to be bound to the correct namespace }
   { 'xml' is allowed to be bound to the correct namespace }
   if ((nsUri = stduri_xml) <> (Prefix = FStdPrefix_xml)) or
   if ((nsUri = stduri_xml) <> (Prefix = FStdPrefix_xml)) or
    (Prefix = FStdPrefix_xmlns) or
    (Prefix = FStdPrefix_xmlns) or
@@ -3190,45 +3187,10 @@ begin
       FatalError('Illegal usage of reserved namespace URI ''%s''', [nsUri]);
       FatalError('Illegal usage of reserved namespace URI ''%s''', [nsUri]);
   end;
   end;
 
 
-  { try reusing an existing binding }
-  b := FFreeBindings;
-  if Assigned(b) then
-    FFreeBindings := b.Next
-  else { no free bindings, create a new one }
-  begin
-    b := TBinding.Create;
-    FBindings.Add(b);
-  end;
+  if (nsUri = '') and not (FXML11 or (Prefix^.Key = '')) then
+    FatalError('Illegal undefining of namespace');  { position - ? }
 
 
-  b.uri := nsUri;
-  b.prefix := Prefix;
-  b.PrevPrefixBinding := Prefix^.Data;
-  if nsUri = '' then
-  begin
-    if (FXML11 or (Prefix = @FDefaultPrefix)) then // prefix being unbound
-      Prefix^.Data := nil
-    else
-      FatalError('Illegal undefining of namespace');  { position - ? }
-  end
-  else
-    Prefix^.Data := b;
-
-  b.Next := Chain;
-  Chain := b;
-end;
-
-procedure TXMLReader.EndNamespaceScope(var Chain: TBinding);
-var
-  b: TBinding;
-begin
-  while Assigned(Chain) do
-  begin
-    b := Chain;
-    Chain := b.next;
-    b.next := FFreeBindings;
-    FFreeBindings := b;
-    b.Prefix^.Data := b.prevPrefixBinding;
-  end;  
+  FNSHelper.BindPrefix(nsURI, Prefix);
 end;
 end;
 
 
 procedure TXMLReader.ProcessNamespaceAtts(Element: TDOMElement);
 procedure TXMLReader.ProcessNamespaceAtts(Element: TDOMElement);
@@ -3240,8 +3202,8 @@ var
   PrefixCount: Integer;
   PrefixCount: Integer;
   b: TBinding;
   b: TBinding;
 begin
 begin
-  if FNesting = Length(FBindingStack) then
-    SetLength(FBindingStack, FNesting * 2);
+  FNSHelper.StartElement;
+
   PrefixCount := 0;
   PrefixCount := 0;
   if Element.HasAttributes then
   if Element.HasAttributes then
   begin
   begin
@@ -3260,13 +3222,12 @@ begin
         begin
         begin
           // TODO: check all consequences of having zero PrefixLength
           // TODO: check all consequences of having zero PrefixLength
           Attr.SetNSI(stduri_xmlns, 0);
           Attr.SetNSI(stduri_xmlns, 0);
-          AddBinding(Attr, @FDefaultPrefix, FBindingStack[FNesting]);
+          AddBinding(Attr, nil, 0);
         end
         end
         else if AttrName^.Key[6] = ':' then
         else if AttrName^.Key[6] = ':' then
         begin
         begin
-          Prefix := FPrefixes.FindOrAdd(@AttrName^.Key[7], Length(AttrName^.Key)-6);
           Attr.SetNSI(stduri_xmlns, 6);
           Attr.SetNSI(stduri_xmlns, 6);
-          AddBinding(Attr, Prefix, FBindingStack[FNesting]);
+          AddBinding(Attr, @AttrName^.Key[7], Length(AttrName^.Key)-6);
         end;
         end;
       end
       end
       else
       else
@@ -3287,15 +3248,15 @@ begin
     FNsAttHash.Init(PrefixCount);
     FNsAttHash.Init(PrefixCount);
     for I := 0 to PrefixCount-1 do
     for I := 0 to PrefixCount-1 do
     begin
     begin
-      AttrName := FWorkAtts[I].Attr.NSI.QName;    
-      Prefix := FPrefixes.FindOrAdd(PWideChar(AttrName^.Key), FWorkAtts[I].PrefixLen-1);
-      b := TBinding(Prefix^.Data);
-      if b = nil then
+      AttrName := FWorkAtts[I].Attr.NSI.QName;
+      if not FNSHelper.IsPrefixBound(PWideChar(AttrName^.Key), FWorkAtts[I].PrefixLen-1, Prefix) then
         FatalError('Unbound prefix "%s"', [Prefix^.Key]);
         FatalError('Unbound prefix "%s"', [Prefix^.Key]);
+
+      b := TBinding(Prefix^.Data);
       { detect duplicates }
       { detect duplicates }
       J := FWorkAtts[I].PrefixLen+1;
       J := FWorkAtts[I].PrefixLen+1;
 
 
-      if FNsAttHash.Locate(@b.uri, @AttrName^.Key[J], Length(AttrName^.Key) - J) then
+      if FNsAttHash.Locate(@b.uri, @AttrName^.Key[J], Length(AttrName^.Key) - J+1) then
         FatalError('Duplicate prefixed attribute');
         FatalError('Duplicate prefixed attribute');
 
 
       // convert Attr into namespaced one (by hack for the time being)
       // convert Attr into namespaced one (by hack for the time being)
@@ -3306,21 +3267,24 @@ begin
   J := Pos(WideChar(':'), Element.NSI.QName^.Key);
   J := Pos(WideChar(':'), Element.NSI.QName^.Key);
   if J > 1 then
   if J > 1 then
   begin
   begin
-    Prefix := FPrefixes.FindOrAdd(PWideChar(Element.NSI.QName^.Key), J-1);
-    if Prefix^.Data = nil then
+    if not FNSHelper.IsPrefixBound(PWideChar(Element.NSI.QName^.Key), J-1, Prefix) then
       FatalError('Unbound prefix "%s"', [Prefix^.Key]);
       FatalError('Unbound prefix "%s"', [Prefix^.Key]);
     b := TBinding(Prefix^.Data);
     b := TBinding(Prefix^.Data);
+    Element.SetNSI(b.uri, J);
   end
   end
-  else if Assigned(FDefaultPrefix.Data) then
-    b := TBinding(FDefaultPrefix.Data)
   else
   else
-    Exit;
-  // convert Element into namespaced one (by hack for the time being)
-  Element.SetNSI(b.uri, J);
+  begin
+    b := FNSHelper.DefaultNSBinding;
+    if Assigned(b) then
+      Element.SetNSI(b.uri, 0);
+  end;
 end;
 end;
 
 
 function TXMLReader.ParseExternalID(out SysID, PubID: WideString;     // [75]
 function TXMLReader.ParseExternalID(out SysID, PubID: WideString;     // [75]
   SysIdOptional: Boolean): Boolean;
   SysIdOptional: Boolean): Boolean;
+var
+  I: Integer;
+  wc: WideChar;
 begin
 begin
   if FSource.Matches('SYSTEM') then
   if FSource.Matches('SYSTEM') then
   begin
   begin
@@ -3331,7 +3295,15 @@ begin
   else if FSource.Matches('PUBLIC') then
   else if FSource.Matches('PUBLIC') then
   begin
   begin
     ExpectWhitespace;
     ExpectWhitespace;
-    SkipPubidLiteral(PubID);
+    SkipQuotedLiteral(PubID);
+    for I := 1 to Length(PubID) do
+    begin
+      wc := PubID[I];
+      if (wc > #255) or not (Char(ord(wc)) in PubidChars) then
+        FatalError('Illegal Public ID literal', -1);
+      if (wc = #10) or (wc = #13) then
+        PubID[I] := #32;
+    end;
     NormalizeSpaces(PubID);
     NormalizeSpaces(PubID);
     if SysIdOptional then
     if SysIdOptional then
       SkipWhitespace
       SkipWhitespace

+ 215 - 9
packages/fcl-xml/src/xmlutils.pp

@@ -16,11 +16,12 @@ unit xmlutils;
 
 
 {$ifdef fpc}{$mode objfpc}{$endif}
 {$ifdef fpc}{$mode objfpc}{$endif}
 {$H+}
 {$H+}
+{$ifopt Q+}{$define overflow_check}{$endif}
 
 
 interface
 interface
 
 
 uses
 uses
-  SysUtils;
+  SysUtils, Classes;
 
 
 function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean; overload;
 function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean; overload;
 function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
 function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
@@ -40,6 +41,7 @@ function WStrLIComp(S1, S2: PWideChar; Len: Integer): Integer;
 type
 type
 {$ifndef fpc}
 {$ifndef fpc}
   PtrInt = LongInt;
   PtrInt = LongInt;
+  TFPList = TList;
 {$endif}  
 {$endif}  
 
 
   PPHashItem = ^PHashItem;
   PPHashItem = ^PHashItem;
@@ -50,7 +52,7 @@ type
     Next: PHashItem;
     Next: PHashItem;
     Data: TObject;
     Data: TObject;
   end;
   end;
-  THashItemArray = array[0..0] of PHashItem;
+  THashItemArray = array[0..MaxInt div sizeof(Pointer)-1] of PHashItem;
   PHashItemArray = ^THashItemArray;
   PHashItemArray = ^THashItemArray;
 
 
   THashForEach = function(Entry: PHashItem; arg: Pointer): Boolean;
   THashForEach = function(Entry: PHashItem; arg: Pointer): Boolean;
@@ -86,7 +88,7 @@ type
     lname: PWideChar;
     lname: PWideChar;
     lnameLen: Integer;
     lnameLen: Integer;
   end;
   end;
-  TExpHashEntryArray = array[0..0] of TExpHashEntry;
+  TExpHashEntryArray = array[0..MaxInt div sizeof(TExpHashEntry)-1] of TExpHashEntry;
   PExpHashEntryArray = ^TExpHashEntryArray;
   PExpHashEntryArray = ^TExpHashEntryArray;
 
 
   TDblHashArray = class(TObject)
   TDblHashArray = class(TObject)
@@ -100,6 +102,43 @@ type
     destructor Destroy; override;
     destructor Destroy; override;
   end;
   end;
 
 
+  TBinding = class
+  public
+    uri: WideString;
+    next: TBinding;
+    prevPrefixBinding: TObject;
+    Prefix: PHashItem;
+  end;
+
+  TAttributeAction = (
+    aaUnchanged,
+    aaPrefix,         // only override the prefix
+    aaBoth            // override prefix and emit namespace definition
+  );
+
+  TNSSupport = class(TObject)
+  private
+    FNesting: Integer;
+    FPrefixSeqNo: Integer;
+    FFreeBindings: TBinding;
+    FBindings: TFPList;
+    FBindingStack: array of TBinding;
+    FPrefixes: THashTable;
+    FDefaultPrefix: THashItem;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure DefineBinding(const Prefix, nsURI: WideString; out Binding: TBinding);
+    function CheckAttribute(const Prefix, nsURI: WideString;
+      out Binding: TBinding): TAttributeAction;
+    function IsPrefixBound(P: PWideChar; Len: Integer; out Prefix: PHashItem): Boolean;
+    function GetPrefix(P: PWideChar; Len: Integer): PHashItem;
+    function BindPrefix(const nsURI: WideString; aPrefix: PHashItem): TBinding;
+    function DefaultNSBinding: TBinding;
+    procedure StartElement;
+    procedure EndElement;
+  end;
+
 {$i names.inc}
 {$i names.inc}
 
 
 implementation
 implementation
@@ -345,7 +384,9 @@ begin
   Result := InitValue;
   Result := InitValue;
   while KeyLen <> 0 do
   while KeyLen <> 0 do
   begin
   begin
+{$ifdef overflow_check}{$q-}{$endif}
     Result := Result * $F4243 xor ord(Key^);
     Result := Result * $F4243 xor ord(Key^);
+{$ifdef overflow_check}{$q+}{$endif}
     Inc(Key);
     Inc(Key);
     Dec(KeyLen);
     Dec(KeyLen);
   end;
   end;
@@ -440,7 +481,7 @@ var
   h: LongWord;
   h: LongWord;
 begin
 begin
   h := Hash(0, Key, KeyLength);
   h := Hash(0, Key, KeyLength);
-  Entry := @FBucket[h mod FBucketCount];
+  Entry := @FBucket^[h mod FBucketCount];
   while Assigned(Entry^) and not ((Entry^^.HashValue = h) and KeyCompare(Entry^^.Key, Key, KeyLength) ) do
   while Assigned(Entry^) and not ((Entry^^.HashValue = h) and KeyCompare(Entry^^.Key, Key, KeyLength) ) do
     Entry := @Entry^^.Next;
     Entry := @Entry^^.Next;
   Found := Assigned(Entry^);
   Found := Assigned(Entry^);
@@ -457,8 +498,7 @@ begin
   else
   else
   begin
   begin
     New(Result);
     New(Result);
-    // SetString for WideStrings trims on zero chars
-    // need to investigate and report
+    // SetString for WideStrings trims on zero chars [fixed, #14740]
     SetLength(Result^.Key, KeyLength);
     SetLength(Result^.Key, KeyLength);
     Move(Key^, Pointer(Result^.Key)^, KeyLength*sizeof(WideChar));
     Move(Key^, Pointer(Result^.Key)^, KeyLength*sizeof(WideChar));
     Result^.HashValue := h;
     Result^.HashValue := h;
@@ -482,7 +522,7 @@ begin
     e := FBucket^[i];
     e := FBucket^[i];
     while Assigned(e) do
     while Assigned(e) do
     begin
     begin
-      chain := @p[e^.HashValue mod NewCapacity];
+      chain := @p^[e^.HashValue mod NewCapacity];
       n := e^.Next;
       n := e^.Next;
       e^.Next := chain^;
       e^.Next := chain^;
       chain^ := e;
       chain^ := e;
@@ -498,7 +538,7 @@ function THashTable.Remove(Entry: PHashItem): Boolean;
 var
 var
   chain: PPHashItem;
   chain: PPHashItem;
 begin
 begin
-  chain := @FBucket[Entry^.HashValue mod FBucketCount];
+  chain := @FBucket^[Entry^.HashValue mod FBucketCount];
   while Assigned(chain^) do
   while Assigned(chain^) do
   begin
   begin
     if chain^ = Entry then
     if chain^ = Entry then
@@ -525,7 +565,7 @@ var
 begin
 begin
   for i := 0 to FBucketCount-1 do
   for i := 0 to FBucketCount-1 do
   begin
   begin
-    chain := @FBucket[i];
+    chain := @FBucket^[i];
     while Assigned(chain^) do
     while Assigned(chain^) do
     begin
     begin
       if chain^^.Data = aData then
       if chain^^.Data = aData then
@@ -625,6 +665,172 @@ begin
   result := False;
   result := False;
 end;
 end;
 
 
+{ TNSSupport }
+
+constructor TNSSupport.Create;
+var
+  b: TBinding;
+begin
+  inherited Create;
+  FPrefixes := THashTable.Create(16, False);
+  FBindings := TFPList.Create;
+  SetLength(FBindingStack, 16);
+
+  { provide implicit binding for the 'xml' prefix }
+  // TODO: move stduri_xml, etc. to this unit, so they are reused.
+  DefineBinding('xml', 'http://www.w3.org/XML/1998/namespace', b);
+end;
+
+destructor TNSSupport.Destroy;
+var
+  I: Integer;
+begin
+  for I := FBindings.Count-1 downto 0 do
+    TObject(FBindings.List^[I]).Free;
+  FBindings.Free;
+  FPrefixes.Free;
+  inherited Destroy;
+end;
+
+function TNSSupport.BindPrefix(const nsURI: WideString; aPrefix: PHashItem): TBinding;
+begin
+  { try to reuse an existing binding }
+  result := FFreeBindings;
+  if Assigned(result) then
+    FFreeBindings := result.Next
+  else { no free bindings, create a new one }
+  begin
+    result := TBinding.Create;
+    FBindings.Add(result);
+  end;
+
+  { link it into chain of bindings at the current element level }
+  result.Next := FBindingStack[FNesting];
+  FBindingStack[FNesting] := result;
+
+  { bind }
+  result.uri := nsURI;
+  result.Prefix := aPrefix;
+  result.PrevPrefixBinding := aPrefix^.Data;
+  aPrefix^.Data := result;
+end;
+
+function TNSSupport.DefaultNSBinding: TBinding;
+begin
+  result := TBinding(FDefaultPrefix.Data);
+end;
+
+procedure TNSSupport.DefineBinding(const Prefix, nsURI: WideString;
+  out Binding: TBinding);
+var
+  Pfx: PHashItem;
+begin
+  Pfx := @FDefaultPrefix;
+  if (nsURI <> '') and (Prefix <> '') then
+    Pfx := FPrefixes.FindOrAdd(PWideChar(Prefix), Length(Prefix));
+  if (Pfx^.Data = nil) or (TBinding(Pfx^.Data).uri <> nsURI) then
+    Binding := BindPrefix(nsURI, Pfx)
+  else
+    Binding := nil;
+end;
+
+function TNSSupport.CheckAttribute(const Prefix, nsURI: WideString;
+  out Binding: TBinding): TAttributeAction;
+var
+  Pfx: PHashItem;
+  I: Integer;
+  b: TBinding;
+  buf: array[0..31] of WideChar;
+  p: PWideChar;
+begin
+  Binding := nil;
+  Pfx := nil;
+  Result := aaUnchanged;
+  if Prefix <> '' then
+    Pfx := FPrefixes.FindOrAdd(PWideChar(Prefix), Length(Prefix))
+  else if nsURI = '' then
+    Exit;
+  { if the prefix is already bound to correct URI, we're done }
+  if Assigned(Pfx) and Assigned(Pfx^.Data) and (TBinding(Pfx^.Data).uri = nsURI) then
+    Exit;
+
+  { see if there's another prefix bound to the target URI }
+  // TODO: should use something faster than linear search
+  for i := FNesting downto 0 do
+  begin
+    b := FBindingStack[i];
+    while Assigned(b) do
+    begin
+      if (b.uri = nsURI) and (b.Prefix <> @FDefaultPrefix) then
+      begin
+        Binding := b;   // found one -> override the attribute's prefix
+        Result := aaPrefix;
+        Exit;
+      end;
+      b := b.Next;
+    end;
+  end;
+  { no prefix, or bound (to wrong URI) -> use generated prefix instead }
+  if (Pfx = nil) or Assigned(Pfx^.Data) then
+  repeat
+    Inc(FPrefixSeqNo);
+    i := FPrefixSeqNo;    // This is just 'NS'+IntToStr(FPrefixSeqNo);
+    p := @Buf[high(Buf)]; // done without using strings
+    while i <> 0 do
+    begin
+      p^ := WideChar(i mod 10+ord('0'));
+      dec(p);
+      i := i div 10;
+    end;
+    p^ := 'S'; dec(p);
+    p^ := 'N';
+    Pfx := FPrefixes.FindOrAdd(p, @Buf[high(Buf)]-p+1);
+  until Pfx^.Data = nil;
+  Binding := BindPrefix(nsURI, Pfx);
+  Result := aaBoth;
+end;
+
+function TNSSupport.IsPrefixBound(P: PWideChar; Len: Integer; out
+  Prefix: PHashItem): Boolean;
+begin
+  Prefix := FPrefixes.FindOrAdd(P, Len);
+  Result := Assigned(Prefix^.Data) and (TBinding(Prefix^.Data).uri <> '');
+end;
+
+function TNSSupport.GetPrefix(P: PWideChar; Len: Integer): PHashItem;
+begin
+  if Assigned(P) and (Len > 0) then
+    Result := FPrefixes.FindOrAdd(P, Len)
+  else
+    Result := @FDefaultPrefix;
+end;
+
+procedure TNSSupport.StartElement;
+begin
+  Inc(FNesting);
+  if FNesting >= Length(FBindingStack) then
+    SetLength(FBindingStack, FNesting * 2);
+end;
+
+procedure TNSSupport.EndElement;
+var
+  b, temp: TBinding;
+begin
+  temp := FBindingStack[FNesting];
+  while Assigned(temp) do
+  begin
+    b := temp;
+    temp := b.next;
+    b.next := FFreeBindings;
+    FFreeBindings := b;
+    b.Prefix^.Data := b.prevPrefixBinding;
+  end;
+  FBindingStack[FNesting] := nil;
+  if FNesting > 0 then
+    Dec(FNesting);
+end;
+
+
 initialization
 initialization
 
 
 finalization
 finalization

+ 217 - 29
packages/fcl-xml/src/xmlwrite.pp

@@ -37,20 +37,33 @@ procedure WriteXML(Element: TDOMNode; AStream: TStream); overload;
 
 
 implementation
 implementation
 
 
-uses SysUtils;
+uses SysUtils, xmlutils;
 
 
 type
 type
-  TSpecialCharCallback = procedure(c: WideChar) of object;
+  TXMLWriter = class;
+  TSpecialCharCallback = procedure(Sender: TXMLWriter; const s: DOMString;
+    var idx: Integer);
+
+  PAttrFixup = ^TAttrFixup;
+  TAttrFixup = record
+    Attr: TDOMNode;
+    Prefix: PHashItem;
+  end;
 
 
   TXMLWriter = class(TObject)
   TXMLWriter = class(TObject)
   private
   private
     FInsideTextNode: Boolean;
     FInsideTextNode: Boolean;
+    FCanonical: Boolean;
     FIndent: WideString;
     FIndent: WideString;
     FIndentCount: Integer;
     FIndentCount: Integer;
     FBuffer: PChar;
     FBuffer: PChar;
     FBufPos: PChar;
     FBufPos: PChar;
     FCapacity: Integer;
     FCapacity: Integer;
     FLineBreak: string;
     FLineBreak: string;
+    FNSHelper: TNSSupport;
+    FAttrFixups: TFPList;
+    FScratch: TFPList;
+    FNSDefs: TFPList;
     procedure wrtChars(Src: PWideChar; Length: Integer);
     procedure wrtChars(Src: PWideChar; Length: Integer);
     procedure IncIndent;
     procedure IncIndent;
     procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
@@ -60,8 +73,8 @@ type
     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;
       const SpecialCharCallback: TSpecialCharCallback);
       const SpecialCharCallback: TSpecialCharCallback);
-    procedure AttrSpecialCharCallback(c: WideChar);
-    procedure TextNodeSpecialCharCallback(c: WideChar);
+    procedure WriteNSDef(B: TBinding);
+    procedure NamespaceFixup(Element: TDOMElement);
   protected
   protected
     procedure Write(const Buffer; Count: Longint); virtual; abstract;
     procedure Write(const Buffer; Count: Longint); virtual; abstract;
     procedure WriteNode(Node: TDOMNode);
     procedure WriteNode(Node: TDOMNode);
@@ -159,10 +172,22 @@ begin
   // 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
   FLineBreak := sLineBreak;
   FLineBreak := sLineBreak;
+  FNSHelper := TNSSupport.Create;
+  FScratch := TFPList.Create;
+  FNSDefs := TFPList.Create;
+  FAttrFixups := TFPList.Create;
 end;
 end;
 
 
 destructor TXMLWriter.Destroy;
 destructor TXMLWriter.Destroy;
+var
+  I: Integer;
 begin
 begin
+  for I := FAttrFixups.Count-1 downto 0 do
+    Dispose(PAttrFixup(FAttrFixups.List^[I]));
+  FAttrFixups.Free;
+  FNSDefs.Free;
+  FScratch.Free;
+  FNSHelper.Free;
   if FBufPos > FBuffer then
   if FBufPos > FBuffer then
     write(FBuffer^, FBufPos-FBuffer);
     write(FBuffer^, FBufPos-FBuffer);
 
 
@@ -304,7 +329,7 @@ begin
     if (s[EndPos] < #255) and (Char(ord(s[EndPos])) in SpecialChars) then
     if (s[EndPos] < #255) and (Char(ord(s[EndPos])) in SpecialChars) then
     begin
     begin
       wrtChars(@s[StartPos], EndPos - StartPos);
       wrtChars(@s[StartPos], EndPos - StartPos);
-      SpecialCharCallback(s[EndPos]);
+      SpecialCharCallback(Self, s, EndPos);
       StartPos := EndPos + 1;
       StartPos := EndPos + 1;
     end;
     end;
     Inc(EndPos);
     Inc(EndPos);
@@ -319,29 +344,31 @@ const
   ltStr = '&lt;';
   ltStr = '&lt;';
   gtStr = '&gt;';
   gtStr = '&gt;';
 
 
-procedure TXMLWriter.AttrSpecialCharCallback(c: WideChar);
+procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
+  var idx: Integer);
 begin
 begin
-  case c of
-    '"': wrtStr(QuotStr);
-    '&': wrtStr(AmpStr);
-    '<': wrtStr(ltStr);
+  case s[idx] of
+    '"': Sender.wrtStr(QuotStr);
+    '&': Sender.wrtStr(AmpStr);
+    '<': Sender.wrtStr(ltStr);
     // Escape whitespace using CharRefs to be consistent with W3 spec § 3.3.3
     // Escape whitespace using CharRefs to be consistent with W3 spec § 3.3.3
-    #9: wrtStr('&#x9;');
-    #10: wrtStr('&#xA;');
-    #13: wrtStr('&#xD;');
+    #9: Sender.wrtStr('&#x9;');
+    #10: Sender.wrtStr('&#xA;');
+    #13: Sender.wrtStr('&#xD;');
   else
   else
-    wrtChr(c);
+    Sender.wrtChr(s[idx]);
   end;
   end;
 end;
 end;
 
 
-procedure TXMLWriter.TextnodeSpecialCharCallback(c: WideChar);
+procedure TextnodeSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
+  var idx: Integer);
 begin
 begin
-  case c of
-    '<': wrtStr(ltStr);
-    '>': wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional
-    '&': wrtStr(AmpStr);
+  case s[idx] of
+    '<': Sender.wrtStr(ltStr);
+    '>': Sender.wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional
+    '&': Sender.wrtStr(AmpStr);
   else
   else
-    wrtChr(c);
+    Sender.wrtChr(s[idx]);
   end;
   end;
 end;
 end;
 
 
@@ -362,6 +389,155 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TXMLWriter.WriteNSDef(B: TBinding);
+begin
+  wrtChars(' xmlns', 6);
+  if B.Prefix^.Key <> '' then
+  begin
+    wrtChr(':');
+    wrtStr(B.Prefix^.Key);
+  end;
+  wrtChars('="', 2);
+  ConvWrite(B.uri, AttrSpecialChars, @AttrSpecialCharCallback);
+  wrtChr('"');
+end;
+
+// clone of system.FPC_WIDESTR_COMPARE which cannot be called directly
+function Compare(const s1, s2: DOMString): integer;
+var
+  maxi, temp: integer;
+begin
+  Result := 0;
+  if pointer(S1) = pointer(S2) then
+    exit;
+  maxi := Length(S1);
+  temp := Length(S2);
+  if maxi > temp then
+    maxi := temp;
+  Result := CompareWord(S1[1], S2[1], maxi);
+  if Result = 0 then
+    Result := Length(S1)-Length(S2);
+end;
+
+function SortNSDefs(Item1, Item2: Pointer): Integer;
+begin
+  Result := Compare(TBinding(Item1).Prefix^.Key, TBinding(Item2).Prefix^.Key);
+end;
+
+function SortAtts(Item1, Item2: Pointer): Integer;
+var
+  p1: PAttrFixup absolute Item1;
+  p2: PAttrFixup absolute Item2;
+  s1, s2: DOMString;
+begin
+  Result := Compare(p1^.Attr.namespaceURI, p2^.Attr.namespaceURI);
+  if Result = 0 then
+  begin
+    // TODO: Must fix the parser so it doesn't produce Level 1 attributes
+    if nfLevel2 in p1^.Attr.Flags then
+      s1 := p1^.Attr.localName
+    else
+      s1 := p1^.Attr.nodeName;
+    if nfLevel2 in p2^.Attr.Flags then
+      s2 := p2^.Attr.localName
+    else
+      s2 := p2^.Attr.nodeName;
+    Result := Compare(s1, s2);
+  end;
+end;
+
+procedure TXMLWriter.NamespaceFixup(Element: TDOMElement);
+var
+  B: TBinding;
+  i, j: Integer;
+  node: TDOMNode;
+  s: DOMString;
+  action: TAttributeAction;
+  p: PAttrFixup;
+begin
+  FScratch.Count := 0;
+  FNSDefs.Count := 0;
+  if Element.hasAttributes then
+  begin
+    j := 0;
+    for i := 0 to Element.Attributes.Length-1 do
+    begin
+      node := Element.Attributes[i];
+      if TDOMNode_NS(node).NSI.NSIndex = 2 then
+      begin
+        if TDOMNode_NS(node).NSI.PrefixLen = 0 then
+          s := ''
+        else
+          s := node.localName;
+        FNSHelper.DefineBinding(s, node.nodeValue, B);
+        if Assigned(B) then  // drop redundant namespace declarations
+          FNSDefs.Add(B);
+      end
+      else if TDOMAttr(node).Specified then
+      begin
+        // obtain a TAttrFixup record (allocate if needed)
+        if j >= FAttrFixups.Count then
+        begin
+          New(p);
+          FAttrFixups.Add(p);
+        end
+        else
+          p := PAttrFixup(FAttrFixups.List^[j]);
+        // add it to the working list
+        p^.Attr := node;
+        p^.Prefix := nil;
+        FScratch.Add(p);
+        Inc(j);
+      end;
+    end;
+  end;
+
+  FNSHelper.DefineBinding(Element.Prefix, Element.namespaceURI, B);
+  if Assigned(B) then
+    FNSDefs.Add(B);
+
+  for i := 0 to FScratch.Count-1 do
+  begin
+    node := PAttrFixup(FScratch.List^[i])^.Attr;
+    action := FNSHelper.CheckAttribute(node.Prefix, node.namespaceURI, B);
+    if action = aaBoth then
+      FNSDefs.Add(B);
+
+    if action in [aaPrefix, aaBoth] then
+      PAttrFixup(FScratch.List^[i])^.Prefix := B.Prefix;
+  end;
+
+  if FCanonical then
+  begin
+    FNSDefs.Sort(@SortNSDefs);
+    FScratch.Sort(@SortAtts);
+  end;
+
+  // now, at last, dump all this stuff.
+  for i := 0 to FNSDefs.Count-1 do
+    WriteNSDef(TBinding(FNSDefs.List^[I]));
+
+  for i := 0 to FScratch.Count-1 do
+  begin
+    wrtChr(' ');
+    with PAttrFixup(FScratch.List^[I])^ do
+    begin
+      if Assigned(Prefix) then
+      begin
+        wrtStr(Prefix^.Key);
+        wrtChr(':');
+        wrtStr(Attr.localName);
+      end
+      else
+        wrtStr(Attr.nodeName);
+
+      wrtChars('="', 2);
+      // TODO: not correct w.r.t. entities
+      ConvWrite(attr.nodeValue, AttrSpecialChars, @AttrSpecialCharCallback);
+      wrtChr('"');
+    end;
+  end;
+end;
 
 
 procedure TXMLWriter.VisitElement(node: TDOMNode);
 procedure TXMLWriter.VisitElement(node: TDOMNode);
 var
 var
@@ -371,10 +547,13 @@ var
 begin
 begin
   if not FInsideTextNode then
   if not FInsideTextNode then
     wrtIndent;
     wrtIndent;
+  FNSHelper.StartElement;
   wrtChr('<');
   wrtChr('<');
   wrtStr(TDOMElement(node).TagName);
   wrtStr(TDOMElement(node).TagName);
-  // FIX: Accessing Attributes was causing them to be created for every element :(
-  if node.HasAttributes then
+
+  if nfLevel2 in node.Flags then
+    NamespaceFixup(TDOMElement(node))
+  else if node.HasAttributes then
     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];
@@ -402,20 +581,26 @@ begin
     wrtStr(TDOMElement(Node).TagName);
     wrtStr(TDOMElement(Node).TagName);
     wrtChr('>');
     wrtChr('>');
   end;
   end;
+  FNSHelper.EndElement;
 end;
 end;
 
 
 procedure TXMLWriter.VisitText(node: TDOMNode);
 procedure TXMLWriter.VisitText(node: TDOMNode);
 begin
 begin
-  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, {$IFDEF FPC}@{$ENDIF}TextnodeSpecialCharCallback);
+  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @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;
-  wrtChars('<![CDATA[', 9);
-  wrtStr(TDOMCharacterData(node).Data);
-  wrtChars(']]>', 3);
+  if FCanonical then
+    ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeSpecialCharCallback)
+  else
+  begin
+    wrtChars('<![CDATA[', 9);
+    wrtStr(TDOMCharacterData(node).Data);
+    wrtChars(']]>', 3);
+  end;
 end;
 end;
 
 
 procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
 procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
@@ -430,8 +615,11 @@ begin
   if not FInsideTextNode then wrtIndent;
   if not FInsideTextNode then wrtIndent;
   wrtStr('<?');
   wrtStr('<?');
   wrtStr(TDOMProcessingInstruction(node).Target);
   wrtStr(TDOMProcessingInstruction(node).Target);
-  wrtChr(' ');
-  wrtStr(TDOMProcessingInstruction(node).Data);
+  if TDOMProcessingInstruction(node).Data <> '' then
+  begin
+    wrtChr(' ');
+    wrtStr(TDOMProcessingInstruction(node).Data);
+  end;
   wrtStr('?>');
   wrtStr('?>');
 end;
 end;
 
 
@@ -500,7 +688,7 @@ begin
       ENTITY_REFERENCE_NODE:
       ENTITY_REFERENCE_NODE:
         VisitEntityRef(Child);
         VisitEntityRef(Child);
       TEXT_NODE:
       TEXT_NODE:
-        ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
+        ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars, @AttrSpecialCharCallback);
     end;
     end;
     Child := Child.NextSibling;
     Child := Child.NextSibling;
   end;
   end;

+ 77 - 61
packages/fcl-xml/src/xpath.pp

@@ -34,8 +34,7 @@ resourcestring
   SVarNoConversion = 'Conversion from %s to %s not possible';
   SVarNoConversion = 'Conversion from %s to %s not possible';
 
 
   { Scanner errors }
   { Scanner errors }
-  SScannerQuotStringIsOpen = 'Ending ''"'' for string not found';
-  SScannerAposStringIsOpen = 'Ending "''" for string not found';
+  SScannerUnclosedString = 'String literal was not closed';
   SScannerInvalidChar = 'Invalid character';
   SScannerInvalidChar = 'Invalid character';
   SScannerMalformedQName = 'Expected "*" or local part after colon';
   SScannerMalformedQName = 'Expected "*" or local part after colon';
   SScannerExpectedVarName = 'Expected variable name after "$"';
   SScannerExpectedVarName = 'Expected variable name after "$"';
@@ -252,6 +251,7 @@ type
     Axis: TAxis;
     Axis: TAxis;
     NodeTestType: TNodeTestType;
     NodeTestType: TNodeTestType;
     NodeTestString: DOMString;
     NodeTestString: DOMString;
+    NSTestString: DOMString;
     Predicates: TXPathNodeArray;
     Predicates: TXPathNodeArray;
     constructor Create(aAxis: TAxis; aTest: TNodeTestType);
     constructor Create(aAxis: TAxis; aTest: TNodeTestType);
     destructor Destroy; override;
     destructor Destroy; override;
@@ -345,6 +345,8 @@ type
   end;
   end;
 
 
 
 
+  TXPathNSResolver = TDOMNode {!!! experimental};
+
 { XPath lexical scanner }
 { XPath lexical scanner }
 
 
   TXPathScanner = class
   TXPathScanner = class
@@ -355,6 +357,7 @@ type
     FTokenStart: DOMPChar;
     FTokenStart: DOMPChar;
     FTokenLength: Integer;
     FTokenLength: Integer;
     FPrefixLength: Integer;
     FPrefixLength: Integer;
+    FResolver: TXPathNSResolver;
     procedure Error(const Msg: String);
     procedure Error(const Msg: String);
     procedure ParsePredicates(var Dest: TXPathNodeArray);
     procedure ParsePredicates(var Dest: TXPathNodeArray);
     procedure ParseStep(Dest: TStep);          // [4]
     procedure ParseStep(Dest: TStep);          // [4]
@@ -471,8 +474,9 @@ type
   public
   public
     { CompleteExpresion specifies wether the parser should check for gargabe
     { CompleteExpresion specifies wether the parser should check for gargabe
       after the recognised part. True => Throw exception if there is garbage }
       after the recognised part. True => Throw exception if there is garbage }
-    constructor Create(AScanner: TXPathScanner; CompleteExpression: Boolean);
-    destructor destroy;override;
+    constructor Create(AScanner: TXPathScanner; CompleteExpression: Boolean;
+      AResolver: TXPathNSResolver = nil);
+    destructor Destroy; override;
     function Evaluate(AContextNode: TDOMNode): TXPathVariable;
     function Evaluate(AContextNode: TDOMNode): TXPathVariable;
     function Evaluate(AContextNode: TDOMNode;
     function Evaluate(AContextNode: TDOMNode;
       AEnvironment: TXPathEnvironment): TXPathVariable;
       AEnvironment: TXPathEnvironment): TXPathVariable;
@@ -480,7 +484,7 @@ type
 
 
 
 
 function EvaluateXPathExpression(const AExpressionString: DOMString;
 function EvaluateXPathExpression(const AExpressionString: DOMString;
-  AContextNode: TDOMNode): TXPathVariable;
+  AContextNode: TDOMNode; AResolver: TXPathNSResolver = nil): TXPathVariable;
 
 
 
 
 // ===================================================================
 // ===================================================================
@@ -524,7 +528,7 @@ var
 begin
 begin
   Val(s, Result, Code);
   Val(s, Result, Code);
 {$push}
 {$push}
-{$r-}
+{$r-,q-}
   if Code <> 0 then
   if Code <> 0 then
     Result := NaN;
     Result := NaN;
 {$pop}
 {$pop}
@@ -762,7 +766,7 @@ begin
           NumberResult := Op1 / Op2;
           NumberResult := Op1 / Op2;
         opMod: if IsNan(Op1) or IsNan(Op2) then
         opMod: if IsNan(Op1) or IsNan(Op2) then
 {$push}
 {$push}
-{$r-}
+{$r-,q-}
           NumberResult := NaN
           NumberResult := NaN
 {$pop}
 {$pop}
         else
         else
@@ -1096,16 +1100,24 @@ var
           (Node.NodeType <> ELEMENT_NODE) then
           (Node.NodeType <> ELEMENT_NODE) then
           exit;
           exit;
       ntName:
       ntName:
-        if Node.NodeName <> NodeTestString then
+        if NSTestString <> '' then
+        begin
+          if Node.namespaceURI <> NSTestString then
+            exit;
+          if (NodeTestString <> '') and (Node.localName <> NodeTestString) then
+            exit;
+        end
+        else if Node.NodeName <> NodeTestString then
           exit;
           exit;
       ntTextNode:
       ntTextNode:
-        if not Node.InheritsFrom(TDOMCharacterData) then
+        if not Node.InheritsFrom(TDOMText) then
           exit;
           exit;
       ntCommentNode:
       ntCommentNode:
         if Node.NodeType <> COMMENT_NODE then
         if Node.NodeType <> COMMENT_NODE then
           exit;
           exit;
       ntPINode:
       ntPINode:
-        if Node.NodeType <> PROCESSING_INSTRUCTION_NODE then
+        if (Node.NodeType <> PROCESSING_INSTRUCTION_NODE) or
+         ((NodeTestString <> '') and (Node.nodeName <> NodeTestString)) then
           exit;
           exit;
     end;
     end;
     if ResultNodes.IndexOf(Node) < 0 then
     if ResultNodes.IndexOf(Node) < 0 then
@@ -1674,6 +1686,8 @@ function TXPathScanner.GetToken: TXPathToken;
     Result := tkNumber;
     Result := tkNumber;
   end;
   end;
 
 
+var
+  Delim: WideChar;
 begin
 begin
   // Skip whitespace
   // Skip whitespace
   while (FCurData[0] < #255) and (char(ord(FCurData[0])) in [#9, #10, #13, ' ']) do
   while (FCurData[0] < #255) and (char(ord(FCurData[0])) in [#9, #10, #13, ' ']) do
@@ -1681,6 +1695,7 @@ begin
 
 
   FTokenStart := FCurData;
   FTokenStart := FCurData;
   FTokenLength := 0;
   FTokenLength := 0;
+  Result := tkInvalid;
 
 
   case FCurData[0] of
   case FCurData[0] of
     #0:
     #0:
@@ -1690,21 +1705,19 @@ begin
       begin
       begin
         Inc(FCurData);
         Inc(FCurData);
         Result := tkNotEqual;
         Result := tkNotEqual;
-      end
-      else
-        Error(SScannerInvalidChar);
-    '"':
+      end;
+    '"', '''':
       begin
       begin
-        FTokenLength := 0;
+        Delim := FCurData^;
         Inc(FCurData);
         Inc(FCurData);
         FTokenStart := FCurData;
         FTokenStart := FCurData;
-        while FCurData[0] <> '"' do
+        while FCurData[0] <> Delim do
         begin
         begin
           if FCurData[0] = #0 then
           if FCurData[0] = #0 then
-            Error(SScannerQuotStringIsOpen);
+            Error(SScannerUnclosedString);
           Inc(FCurData);
           Inc(FCurData);
-          Inc(FTokenLength);
         end;
         end;
+        FTokenLength := FCurData-FTokenStart;
         Result := tkString;
         Result := tkString;
       end;
       end;
     '$':
     '$':
@@ -1717,20 +1730,6 @@ begin
           Error(SScannerExpectedVarName);
           Error(SScannerExpectedVarName);
         Exit;
         Exit;
       end;
       end;
-    '''':
-      begin
-        FTokenLength := 0;
-        Inc(FCurData);
-        FTokenStart := FCurData;
-        while FCurData[0] <> '''' do
-        begin
-          if FCurData[0] = #0 then
-            Error(SScannerAposStringIsOpen);
-          Inc(FCurData);
-          Inc(FTokenLength);
-        end;
-        Result := tkString;
-      end;
     '(':
     '(':
       Result := tkLeftBracket;
       Result := tkLeftBracket;
     ')':
     ')':
@@ -1766,8 +1765,7 @@ begin
       begin
       begin
         Inc(FCurData);
         Inc(FCurData);
         Result := tkColonColon;
         Result := tkColonColon;
-      end else
-        Error(SScannerInvalidChar);  // single colons are handled as part of identifier
+      end;
     '<':
     '<':
       if FCurData[1] = '=' then
       if FCurData[1] = '=' then
       begin
       begin
@@ -1809,13 +1807,13 @@ begin
       end
       end
       else
       else
         Error(SScannerMalformedQName);
         Error(SScannerMalformedQName);
-    end
-    else
-      Error(SScannerInvalidChar);
+    end;
   end;
   end;
 
 
+  if Result = tkInvalid then
+    Error(SScannerInvalidChar);
   // We have processed at least one character now; eat it:
   // We have processed at least one character now; eat it:
-  if Result <> tkEndOfStream then
+  if Result > tkEndOfStream then
     Inc(FCurData);
     Inc(FCurData);
 end;
 end;
 
 
@@ -1923,7 +1921,12 @@ begin
     else if CurToken = tkNSNameTest then // [37] NameTest, second case
     else if CurToken = tkNSNameTest then // [37] NameTest, second case
     begin
     begin
       NextToken;
       NextToken;
-      // TODO: resolve the prefix and set Dest properties
+      if Assigned(FResolver) then
+        Dest.NSTestString := FResolver.lookupNamespaceURI(CurTokenString);
+      if Dest.NSTestString = '' then
+        // !! localization disrupted by DOM exception specifics
+        raise EDOMNamespace.Create('TXPathScanner.ParseStep');
+      Dest.NodeTestType := ntName;
     end
     end
     else if CurToken = tkIdentifier then
     else if CurToken = tkIdentifier then
     begin
     begin
@@ -1945,7 +1948,6 @@ begin
           NextToken;   { skip '('; we know it's there }
           NextToken;   { skip '('; we know it's there }
           if NextToken = tkString then
           if NextToken = tkString then
           begin
           begin
-            // TODO: Handle processing-instruction('name') constructs
             Dest.NodeTestString := CurTokenString;
             Dest.NodeTestString := CurTokenString;
             NextToken;
             NextToken;
           end;
           end;
@@ -1964,14 +1966,17 @@ begin
       end
       end
       else  // [37] NameTest, third case
       else  // [37] NameTest, third case
       begin
       begin
-        // !!!: Doesn't support namespaces yet
-        // (this will have to wait until the DOM unit supports them)
         Dest.NodeTestType := ntName;
         Dest.NodeTestType := ntName;
-        Dest.NodeTestString := CurTokenString;
         if FPrefixLength > 0 then
         if FPrefixLength > 0 then
         begin
         begin
-          // TODO: resolve the prefix and set Dest properties
-        end;
+          if Assigned(FResolver) then
+            Dest.NSTestString := FResolver.lookupNamespaceURI(Copy(CurTokenString, 1, FPrefixLength));
+          if Dest.NSTestString = '' then
+            raise EDOMNamespace.Create('TXPathScanner.ParseStep');
+          Dest.NodeTestString := Copy(CurTokenString, FPrefixLength+2, MaxInt);
+        end
+        else
+          Dest.NodeTestString := CurTokenString;
         NextToken;
         NextToken;
       end;
       end;
     end
     end
@@ -2472,11 +2477,12 @@ var
   begin
   begin
     Head := 1;
     Head := 1;
     L := Length(s);
     L := Length(s);
-    while (Head <= L) and IsXmlWhiteSpace(s[Head]) do
-      Inc(Head);
 
 
     while Head <= L do
     while Head <= L do
     begin
     begin
+      while (Head <= L) and IsXmlWhiteSpace(s[Head]) do
+        Inc(Head);
+
       Tail := Head;
       Tail := Head;
       while (Tail <= L) and not IsXmlWhiteSpace(s[Tail]) do
       while (Tail <= L) and not IsXmlWhiteSpace(s[Tail]) do
         Inc(Tail);
         Inc(Tail);
@@ -2486,8 +2492,6 @@ var
         ns.Add(Element);
         ns.Add(Element);
 
 
       Head := Tail;
       Head := Tail;
-      while IsXmlWhiteSpace(s[Head]) do
-        Inc(Head);
     end;
     end;
   end;
   end;
 
 
@@ -2563,17 +2567,27 @@ end;
 
 
 function TXPathEnvironment.xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
 function TXPathEnvironment.xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
 var
 var
+  n: TDOMNode;
   NodeSet: TNodeSet;
   NodeSet: TNodeSet;
+  s: DOMString;
 begin
 begin
-// TODO: arg is optional, omission case must be handled
-  if Args.Count <> 1 then
+  if Args.Count > 1 then
     EvaluationError(SEvalInvalidArgCount);
     EvaluationError(SEvalInvalidArgCount);
-  NodeSet := TXPathVariable(Args[0]).AsNodeSet;
-  if NodeSet.Count = 0 then
-    Result := TXPathStringVariable.Create('')
+  n := nil;
+  if Args.Count = 0 then
+    n := Context.ContextNode
   else
   else
-    // !!!: Probably not really correct regarding namespaces...
-    Result := TXPathStringVariable.Create(TDOMNode(NodeSet[0]).NodeName);
+  begin
+    NodeSet := TXPathVariable(Args[0]).AsNodeSet;
+    if NodeSet.Count > 0 then
+      n := TDOMNode(NodeSet[0]);
+  end;
+  // TODO: probably this isn't correct. XPath name() isn't the same as DOM nodeName.
+  if Assigned(n) then
+    s := n.nodeName
+  else
+    s := '';
+  Result := TXPathStringVariable.Create(s);
 end;
 end;
 
 
 function TXPathEnvironment.xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
 function TXPathEnvironment.xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
@@ -2686,9 +2700,10 @@ begin
       n2 := floor(0.5 + e2);
       n2 := floor(0.5 + e2);
   end;
   end;
   i := Max(n1, 1);
   i := Max(n1, 1);
-  n2 := n2 + n1 - i;
   if empty then
   if empty then
-    n2 := -1;
+    n2 := -1
+  else if n2 < MaxInt then
+    n2 := n2 + (n1 - i);
   Result := TXPathStringVariable.Create(Copy(s, i, n2));
   Result := TXPathStringVariable.Create(Copy(s, i, n2));
 end;
 end;
 
 
@@ -2853,9 +2868,10 @@ end;
 { TXPathExpression }
 { TXPathExpression }
 
 
 constructor TXPathExpression.Create(AScanner: TXPathScanner;
 constructor TXPathExpression.Create(AScanner: TXPathScanner;
-  CompleteExpression: Boolean);
+  CompleteExpression: Boolean; AResolver: TXPathNSResolver);
 begin
 begin
   inherited Create;
   inherited Create;
+  AScanner.FResolver := AResolver;
   FRootNode := AScanner.ParseOrExpr;
   FRootNode := AScanner.ParseOrExpr;
   if CompleteExpression and (AScanner.CurToken <> tkEndOfStream) then
   if CompleteExpression and (AScanner.CurToken <> tkEndOfStream) then
     EvaluationError(SParserGarbageAfterExpression);
     EvaluationError(SParserGarbageAfterExpression);
@@ -2901,14 +2917,14 @@ begin
 end;
 end;
 
 
 function EvaluateXPathExpression(const AExpressionString: DOMString;
 function EvaluateXPathExpression(const AExpressionString: DOMString;
-  AContextNode: TDOMNode): TXPathVariable;
+  AContextNode: TDOMNode; AResolver: TXPathNSResolver): TXPathVariable;
 var
 var
   Scanner: TXPathScanner;
   Scanner: TXPathScanner;
   Expression: TXPathExpression;
   Expression: TXPathExpression;
 begin
 begin
   Scanner := TXPathScanner.Create(AExpressionString);
   Scanner := TXPathScanner.Create(AExpressionString);
   try
   try
-    Expression := TXPathExpression.Create(Scanner, True);
+    Expression := TXPathExpression.Create(Scanner, True, AResolver);
     try
     try
       Result := Expression.Evaluate(AContextNode);
       Result := Expression.Evaluate(AContextNode);
     finally
     finally

+ 7 - 2
packages/fcl-xml/tests/api.xml

@@ -262,6 +262,7 @@
 <item id="isId"/>
 <item id="isId"/>
 <item id="documentURI" type="prop"/>
 <item id="documentURI" type="prop"/>
 <!--
 <!--
+<item id="baseURI"/>
 // assertNotEquals
 // assertNotEquals
 // assertLowerSeverity
 // assertLowerSeverity
 
 
@@ -273,9 +274,13 @@
 <item id="lookupNamespaceURI">
 <item id="lookupNamespaceURI">
   <arg>prefix</arg>
   <arg>prefix</arg>
 </item>
 </item>
+<item id="lookupPrefix">
+  <arg>namespaceURI</arg>
+</item>
+<item id="isDefaultNamespace">
+  <arg>namespaceURI</arg>
+</item>  
 <!--
 <!--
-<item id="lookupPrefix"/>
-<item id="isDefaultNamespace"/>
 <item id="adoptNode"/>
 <item id="adoptNode"/>
 <item id="renameNode"/>
 <item id="renameNode"/>
 <item id="replaceWholeText"/>
 <item id="replaceWholeText"/>

+ 1 - 1
packages/fcl-xml/tests/domunit.pp

@@ -323,7 +323,7 @@ begin
   src := TXMLInputSource.Create(data);
   src := TXMLInputSource.Create(data);
   try
   try
     FParser.Parse(src, TXMLDocument(Doc));
     FParser.Parse(src, TXMLDocument(Doc));
-    GC(Doc);
+    GC(TObject(Doc));
   finally
   finally
     src.Free;
     src.Free;
   end;
   end;

+ 114 - 1
packages/fcl-xml/tests/extras.pp

@@ -18,7 +18,7 @@ unit extras;
 interface
 interface
 
 
 uses
 uses
-  SysUtils, Classes, DOM, xmlread, domunit, testregistry;
+  SysUtils, Classes, DOM, xmlread, xmlwrite, domunit, testregistry;
 
 
 implementation
 implementation
 
 
@@ -29,6 +29,9 @@ type
     procedure attr_ownership02;
     procedure attr_ownership02;
     procedure attr_ownership03;
     procedure attr_ownership03;
     procedure attr_ownership04;
     procedure attr_ownership04;
+    procedure nsFixup1;
+    procedure nsFixup2;
+    procedure nsFixup3;
   end;
   end;
 
 
 { TDOMTestExtra }
 { TDOMTestExtra }
@@ -113,7 +116,117 @@ begin
   AssertEquals('ownerElement2', el, attr2.OwnerElement);
   AssertEquals('ownerElement2', el, attr2.OwnerElement);
 end;
 end;
 
 
+const
+  nsURI1 = 'http://www.example.com/ns1';
+  nsURI2 = 'http://www.example.com/ns2';
 
 
+// verify the namespace fixup with two nested elements
+// (same localName, different nsURI, and no prefixes)
+procedure TDOMTestExtra.nsFixup1;
+var
+  domImpl: TDOMImplementation;
+  origDoc: TDOMDocument;
+  parsedDoc: TDOMDocument;
+  docElem: TDOMElement;
+  el: TDOMElement;
+  stream: TStringStream;
+  list: TDOMNodeList;
+begin
+  FParser.Options.Namespaces := True;
+  domImpl := GetImplementation;
+  origDoc := domImpl.createDocument(nsURI1, 'test', nil);
+  GC(origDoc);
+  docElem := origDoc.documentElement;
+  el := origDoc.CreateElementNS(nsURI2, 'test');
+  docElem.AppendChild(el);
+
+  stream := TStringStream.Create('');
+  GC(stream);
+  writeXML(origDoc, stream);
+  LoadStringData(parsedDoc, stream.DataString);
+
+  docElem := parsedDoc.documentElement;
+  assertEquals('docElemLocalName', 'test', docElem.localName);
+  assertEquals('docElemNS', nsURI1, docElem.namespaceURI);
+
+  list := docElem.GetElementsByTagNameNS(nsURI2, '*');
+  assertEquals('ns2_elementCount', 1, list.Length);
+  el := TDOMElement(list[0]);
+  assertEquals('ns2_nodeName', 'test', el.nodeName);
+end;
+
+// verify the namespace fixup with two nested elements
+// (same localName, different nsURI, different prefixes)
+procedure TDOMTestExtra.nsFixup2;
+var
+  domImpl: TDOMImplementation;
+  origDoc: TDOMDocument;
+  parsedDoc: TDOMDocument;
+  docElem: TDOMElement;
+  el: TDOMElement;
+  stream: TStringStream;
+  list: TDOMNodeList;
+begin
+  FParser.Options.Namespaces := True;
+  domImpl := GetImplementation;
+  origDoc := domImpl.createDocument(nsURI1, 'a:test', nil);
+  GC(origDoc);
+  docElem := origDoc.documentElement;
+  el := origDoc.CreateElementNS(nsURI2, 'b:test');
+  docElem.AppendChild(el);
+
+  stream := TStringStream.Create('');
+  GC(stream);
+  writeXML(origDoc, stream);
+  LoadStringData(parsedDoc, stream.DataString);
+
+  docElem := parsedDoc.documentElement;
+  assertEquals('docElemLocalName', 'test', docElem.localName);
+  assertEquals('docElemNS', nsURI1, docElem.namespaceURI);
+
+  list := docElem.GetElementsByTagNameNS(nsURI2, '*');
+  assertEquals('ns2_elementCount', 1, list.Length);
+  el := TDOMElement(list[0]);
+  assertEquals('ns2_nodeName', 'b:test', el.nodeName);
+end;
+
+// verify the namespace fixup with two nested elements and an attribute
+// attribute's prefix must change to that of document element
+procedure TDOMTestExtra.nsFixup3;
+var
+  domImpl: TDOMImplementation;
+  origDoc: TDOMDocument;
+  parsedDoc: TDOMDocument;
+  docElem: TDOMElement;
+  el: TDOMElement;
+  stream: TStringStream;
+  list: TDOMNodeList;
+  attr: TDOMAttr;
+begin
+  FParser.Options.Namespaces := True;
+  domImpl := GetImplementation;
+  origDoc := domImpl.createDocument(nsURI1, 'a:test', nil);
+  GC(origDoc);
+  docElem := origDoc.documentElement;
+  el := origDoc.CreateElementNS(nsURI2, 'b:test');
+  docElem.AppendChild(el);
+  el.SetAttributeNS(nsURI1, 'test:attr', 'test value');
+
+  stream := TStringStream.Create('');
+  GC(stream);
+  writeXML(origDoc, stream);
+  LoadStringData(parsedDoc, stream.DataString);
+
+  docElem := parsedDoc.documentElement;
+  assertEquals('docElemLocalName', 'test', docElem.localName);
+  assertEquals('docElemNS', nsURI1, docElem.namespaceURI);
+
+  list := docElem.GetElementsByTagNameNS(nsURI2, '*');
+  assertEquals('ns2_elementCount', 1, list.Length);
+  el := TDOMElement(list[0]);
+  attr := el.GetAttributeNodeNS(nsURI1, 'attr');
+  assertEquals('attr_nodeName', 'a:attr', attr.nodeName);
+end;
 
 
 
 
 initialization
 initialization

+ 6 - 12
packages/fcl-xml/tests/extras2.pp

@@ -88,10 +88,7 @@ var
   nodeValue: DOMString;
   nodeValue: DOMString;
   length: Integer;
   length: Integer;
 begin
 begin
-// canonical form: PreserveWhitespace, Namespaces, NamespaceDeclarations = True;
-//                 Entities, CDSections = False;
-  FParser.Options.PreserveWhitespace := True;
-  FParser.Options.Namespaces := True;
+  FParser.Options.CanonicalForm := True;
   LoadStringData(doc, canonicform01);
   LoadStringData(doc, canonicform01);
   begin
   begin
     node := TDOMNode(doc).firstChild;
     node := TDOMNode(doc).firstChild;
@@ -152,10 +149,7 @@ var
   nodeValue: DOMString;
   nodeValue: DOMString;
   length: Integer;
   length: Integer;
 begin
 begin
-// canonical form: PreserveWhitespace, Namespaces, NamespaceDeclarations = True;
-//                 Entities, CDSections = False;
-  FParser.Options.PreserveWhitespace := True;
-  FParser.Options.Namespaces := True;
+  FParser.Options.CanonicalForm := True;
   FParser.Options.IgnoreComments := True;
   FParser.Options.IgnoreComments := True;
   LoadStringData(doc, canonicform01);
   LoadStringData(doc, canonicform01);
   begin
   begin
@@ -198,8 +192,7 @@ var
   divEl: TDOMElement;
   divEl: TDOMElement;
   node: TDOMNode;
   node: TDOMNode;
 begin
 begin
-  FParser.Options.PreserveWhitespace := True;
-  FParser.Options.Namespaces := True;
+  FParser.Options.CanonicalForm := True;
   LoadStringData(doc, canonicform03);
   LoadStringData(doc, canonicform03);
 
 
   divList := doc.getElementsByTagName('div');
   divList := doc.getElementsByTagName('div');
@@ -220,8 +213,7 @@ var
   attrSpecified: Boolean;
   attrSpecified: Boolean;
   attrValue: DOMString;
   attrValue: DOMString;
 begin
 begin
-  FParser.Options.PreserveWhitespace := True;
-  FParser.Options.Namespaces := True;
+  FParser.Options.CanonicalForm := True;
   LoadStringData(doc, canonicform03);
   LoadStringData(doc, canonicform03);
 
 
   elemList := doc.getElementsByTagName('acronym');
   elemList := doc.getElementsByTagName('acronym');
@@ -252,6 +244,7 @@ begin
   FParser.Options.Namespaces := True;
   FParser.Options.Namespaces := True;
   domImpl := GetImplementation;
   domImpl := GetImplementation;
   origDoc := domImpl.createDocument(namespaceURI, 'test', nil);
   origDoc := domImpl.createDocument(namespaceURI, 'test', nil);
+  GC(origDoc);
   docElem := origDoc.documentElement;
   docElem := origDoc.documentElement;
   docElem.setAttributeNS(namespaceURI, 'attr', 'test value');
   docElem.setAttributeNS(namespaceURI, 'attr', 'test value');
 
 
@@ -288,6 +281,7 @@ begin
   FParser.Options.Namespaces := True;
   FParser.Options.Namespaces := True;
   domImpl := GetImplementation;
   domImpl := GetImplementation;
   origDoc := domImpl.createDocument(namespaceURI, 'test', nil);
   origDoc := domImpl.createDocument(namespaceURI, 'test', nil);
+  GC(origDoc);
   docElem := origDoc.documentElement;
   docElem := origDoc.documentElement;
   docElem.setAttributeNS(namespaceURI, 'test:attr', 'test value');
   docElem.setAttributeNS(namespaceURI, 'test:attr', 'test value');
 
 

+ 6 - 4
packages/fcl-xml/tests/xmlts.pp

@@ -168,7 +168,7 @@ begin
   try
   try
     for I := 0 to tables.Count-1 do
     for I := 0 to tables.Count-1 do
     begin
     begin
-      el := TDOMElement(tables.Item[I]);
+      el := TDOMElement(tables[I]);
       id := el['id'];
       id := el['id'];
       if id = 'valid' then
       if id = 'valid' then
         table_valid := el
         table_valid := el
@@ -208,7 +208,7 @@ begin
   Index := 0;
   Index := 0;
 
 
   repeat
   repeat
-    Child := Children.Item[Index];
+    Child := Children[Index];
     if Child = nil then Break;
     if Child = nil then Break;
     Inc(index);
     Inc(index);
 
 
@@ -314,7 +314,7 @@ begin
   writeln('Testing, validation = ', FValidating);
   writeln('Testing, validation = ', FValidating);
   try
   try
     for I := 0 to Cases.Count-1 do
     for I := 0 to Cases.Count-1 do
-      RunTest(Cases.Item[I] as TDOMElement);
+      RunTest(Cases[I] as TDOMElement);
     I := Cases.Count;
     I := Cases.Count;
   finally
   finally
     Cases.Free;
     Cases.Free;
@@ -349,6 +349,7 @@ var
   Positive: Boolean;
   Positive: Boolean;
   outURI: UTF8string;
   outURI: UTF8string;
   FailMsg: string;
   FailMsg: string;
+  ExceptionClass: TClass;
   docNode, refNode: TDOMNode;
   docNode, refNode: TDOMNode;
   docMap, refMap: TDOMNamedNodeMap;
   docMap, refMap: TDOMNamedNodeMap;
   docN, refN: TDOMNotation;
   docN, refN: TDOMNotation;
@@ -407,6 +408,7 @@ begin
       on E: Exception do
       on E: Exception do
         if E.ClassType <> EAbort then
         if E.ClassType <> EAbort then
         begin
         begin
+          ExceptionClass := E.ClassType;
           FailMsg := E.Message;
           FailMsg := E.Message;
           FValError := '';
           FValError := '';
         end;
         end;
@@ -435,7 +437,7 @@ begin
         if FailMsg <> '' then  // Fatal error
         if FailMsg <> '' then  // Fatal error
         begin
         begin
           { outside not-wf category it is a test failure }
           { outside not-wf category it is a test failure }
-          if table <> table_not_wf then
+          if (table <> table_not_wf) or (ExceptionClass <> EXMLReadError) then
           begin
           begin
             Inc(FFailCount);
             Inc(FFailCount);
             Diagnose(Element, table, dcFail, FailMsg);
             Diagnose(Element, table, dcFail, FailMsg);

+ 70 - 17
packages/fcl-xml/tests/xpathts.pp

@@ -33,6 +33,16 @@ type
     rtBool:   (b: Boolean);
     rtBool:   (b: Boolean);
   end;
   end;
 
 
+  TTestRec3 = record
+    data: string;                // UTF-8 encoded
+    re: string;
+    expr: DOMString;
+  case rt: TResultType of
+    rtString, rtNodeStr: (s: DOMPChar);   // cannot use DOMString here
+    rtNumber: (n: Extended);
+    rtBool:   (b: Boolean);
+  end;
+
 {$warnings off}
 {$warnings off}
 const
 const
   BaseTests: array[0..4] of TTestRec = (
   BaseTests: array[0..4] of TTestRec = (
@@ -542,7 +552,7 @@ const
   '<b ns1:attrib2="test"/>'#10+
   '<b ns1:attrib2="test"/>'#10+
   '</doc>';
   '</doc>';
 
 
-  StringTests: array[0..84] of TTestRec = (             // numbers refer to xalan/string/stringXX
+  StringTests: array[0..74] of TTestRec = (             // numbers refer to xalan/string/stringXX
     (expr: 'string(0)';       rt: rtString; s: '0'),
     (expr: 'string(0)';       rt: rtString; s: '0'),
     (expr: 'string(5)';       rt: rtString; s: '5'),    // #38/39
     (expr: 'string(5)';       rt: rtString; s: '5'),    // #38/39
     (expr: 'string(0.5)';     rt: rtString; s: '0.5'),
     (expr: 'string(0.5)';     rt: rtString; s: '0.5'),
@@ -573,8 +583,6 @@ const
     (expr: 'starts-with("", "")';            rt: rtBool; b: True),     // #49
     (expr: 'starts-with("", "")';            rt: rtBool; b: True),     // #49
     (expr: 'starts-with(true(), "tr")';      rt: rtBool; b: True),     // #50
     (expr: 'starts-with(true(), "tr")';      rt: rtBool; b: True),     // #50
 
 
-
-
     (expr: 'contains("tititototata","titi")'; rt: rtBool; b: True),
     (expr: 'contains("tititototata","titi")'; rt: rtBool; b: True),
     (expr: 'contains("tititototata","toto")'; rt: rtBool; b: True),
     (expr: 'contains("tititototata","toto")'; rt: rtBool; b: True),
     (expr: 'contains("tititototata","tata")'; rt: rtBool; b: True),
     (expr: 'contains("tititototata","tata")'; rt: rtBool; b: True),
@@ -625,18 +633,6 @@ const
     (expr: 'translate("--aaa--","abc-","ABC")'; rt: rtString; s: 'AAA'),
     (expr: 'translate("--aaa--","abc-","ABC")'; rt: rtString; s: 'AAA'),
     (expr: 'translate("ddaaadddd","abcd","ABCxy")'; rt: rtString; s: 'xxAAAxxxx'),   // #96
     (expr: 'translate("ddaaadddd","abcd","ABCxy")'; rt: rtString; s: 'xxAAAxxxx'),   // #96
 
 
-    (data: str30; expr: 'namespace-uri(baz1:a/@baz2:attrib1)'; rt: rtString; s: ''), // #30
-    (data: str30; expr: 'namespace-uri(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'http://xsl.lotus.com/ns1'), // #31
-    (data: str30; expr: 'name(*)'; rt: rtString; s: 'ns1:a'),       // #32
-    (data: str30; expr: 'name(baz1:a)'; rt: rtString; s: 'ns1:a'),  // #33
-    (data: str30; expr: 'name(baz2:b)'; rt: rtString; s: 'b'),      // #34
-    (data: str30; expr: 'name(baz1:a/@baz2:attrib1)'; rt: rtString; s: ''),            // #35
-    (data: str30; expr: 'name(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'ns1:attrib2'), // #36
-
-    (data: str30; expr: 'local-name(baz2:b)'; rt: rtString; s: 'b'), // namespace07
-    (data: str30; expr: 'local-name(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'attrib2'), // namespace09
-    (data: str30; expr: 'local-name()'; rt: rtString; s: 'doc'),      // namespace26
-    
     // tests for number->string conversions at boundary conditions
     // tests for number->string conversions at boundary conditions
     (expr: 'string(123456789012345678)';     rt: rtString; s: '123456789012345680'),    // #132.1
     (expr: 'string(123456789012345678)';     rt: rtString; s: '123456789012345680'),    // #132.1
     (expr: 'string(-123456789012345678)';    rt: rtString; s: '-123456789012345680'),   // #132.2
     (expr: 'string(-123456789012345678)';    rt: rtString; s: '-123456789012345680'),   // #132.2
@@ -650,7 +646,23 @@ const
     (expr: 'string(-.0000000000000000000000000000000000000000123456789)'; rt: rtString; // #135.2
     (expr: 'string(-.0000000000000000000000000000000000000000123456789)'; rt: rtString; // #135.2
       s: '-0.0000000000000000000000000000000000000000123456789')
       s: '-0.0000000000000000000000000000000000000000123456789')
   );
   );
-  
+
+  res1 = '<foo xmlns:baz1="http://xsl.lotus.com/ns1" xmlns:baz2="http://xsl.lotus.com/ns2"/>';
+
+  nameTests: array[0..9] of TTestRec3 = (
+    (data: str30; re: res1; expr: 'namespace-uri(baz1:a/@baz2:attrib1)'; rt: rtString; s: ''), // #30
+    (data: str30; re: res1; expr: 'namespace-uri(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'http://xsl.lotus.com/ns1'), // #31
+    (data: str30; re: res1; expr: 'name(*)'; rt: rtString; s: 'ns1:a'),       // #32
+    (data: str30; re: res1; expr: 'name(baz1:a)'; rt: rtString; s: 'ns1:a'),  // #33
+    (data: str30; re: res1; expr: 'name(baz2:b)'; rt: rtString; s: 'b'),      // #34
+    (data: str30; re: res1; expr: 'name(baz1:a/@baz2:attrib1)'; rt: rtString; s: ''),            // #35
+    (data: str30; re: res1; expr: 'name(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'ns1:attrib2'), // #36
+
+    (data: str30; re: res1; expr: 'local-name(baz2:b)'; rt: rtString; s: 'b'), // namespace07
+    (data: str30; re: res1; expr: 'local-name(baz2:b/@baz1:attrib2)'; rt: rtString; s: 'attrib2'), // namespace09
+    (data: str30; re: res1; expr: 'local-name()'; rt: rtString; s: 'doc')      // namespace26
+  );
+
   ax114='<doc>'+
   ax114='<doc>'+
   '<foo att1="c">'+
   '<foo att1="c">'+
   '  <foo att1="b">'+
   '  <foo att1="b">'+
@@ -814,8 +826,47 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure DoSuite3(const tests: array of TTestRec3);
+var
+  i: Integer;
+  doc: TXMLDocument;
+  rslt: TXPathVariable;
+  nsdoc: TXMLDocument;
+  temp: TTestRec;
+begin
+  for i := 0 to High(tests) do
+  begin
+    doc := ParseString(tests[i].data);
+    try
+      nsdoc := ParseString(tests[i].re);
+      try
+        try
+          rslt := EvaluateXPathExpression(tests[i].expr, doc.DocumentElement, nsdoc.DocumentElement);
+          try
+            temp.data := tests[i].data;
+            temp.expr := tests[i].expr;
+            temp.rt := tests[i].rt;
+            temp.n := tests[i].n;
+            CheckResult(temp, rslt);
+          finally
+            rslt.Free;
+          end;
+        except
+          writeln;
+          writeln('Failed: ', tests[i].expr);
+          SysUtils.ShowException(ExceptObject, ExceptAddr);
+          Inc(FailCount);
+        end;
+      finally
+        nsdoc.Free;
+      end;
+    finally
+      doc.Free;
+    end;
+  end;
+end;
+
 begin
 begin
-  DecimalSeparator := '.';
   DoSuite(BaseTests);
   DoSuite(BaseTests);
   DoSuite(CompareTests);
   DoSuite(CompareTests);
   DoSuite(NodesetCompareTests);  
   DoSuite(NodesetCompareTests);  
@@ -825,6 +876,8 @@ begin
   DoSuite(StringTests);
   DoSuite(StringTests);
   DoSuite(AxesTests);
   DoSuite(AxesTests);
 
 
+  DoSuite3(nameTests);
+
   writeln;
   writeln;
   writeln('Total failed tests: ', FailCount);
   writeln('Total failed tests: ', FailCount);
 end.
 end.