Browse Source

- TDOMNode will delete itself from parent before destruction
- TDOMNamedNodeMap:
added checking of NodeType of nodes being added;
made sorted in order to speed up searching.
- Added TDOMEntityReference.CloneNode.

git-svn-id: trunk@5017 -

michael 19 years ago
parent
commit
fdc32bdcab
1 changed files with 174 additions and 162 deletions
  1. 174 162
      fcl/xml/dom.pp

+ 174 - 162
fcl/xml/dom.pp

@@ -187,7 +187,7 @@ type
   This lowers memory usage and also obsoletes most constructors,
   This lowers memory usage and also obsoletes most constructors,
   at a slight performance penalty. However, NodeName and NodeValue are
   at a slight performance penalty. However, NodeName and NodeValue are
   accessible via fields using specialized properties of descendant classes,
   accessible via fields using specialized properties of descendant classes,
-  e.g. TDOMElement.TagName, TDOMCharacterData.Data etc.} 
+  e.g. TDOMElement.TagName, TDOMCharacterData.Data etc.}
 
 
   TDOMNode = class
   TDOMNode = class
   protected
   protected
@@ -207,9 +207,10 @@ type
     procedure SetTextContent(const AValue: DOMString); virtual;
     procedure SetTextContent(const AValue: DOMString); virtual;
   public
   public
     constructor Create(AOwner: TDOMDocument);
     constructor Create(AOwner: TDOMDocument);
+    destructor Destroy; override;
 
 
     // Free NodeList with TDOMNodeList.Release!
     // Free NodeList with TDOMNodeList.Release!
-    function GetChildNodes: TDOMNodeList; virtual;  // why virtual?
+    function GetChildNodes: TDOMNodeList;
 
 
     property NodeName: DOMString read GetNodeName;
     property NodeName: DOMString read GetNodeName;
     property NodeValue: DOMString read GetNodeValue write SetNodeValue;
     property NodeValue: DOMString read GetNodeValue write SetNodeValue;
@@ -236,7 +237,7 @@ type
     function Supports(const Feature, Version: DOMString): Boolean;
     function Supports(const Feature, Version: DOMString): Boolean;
     *)
     *)
     function HasAttributes: Boolean; virtual;
     function HasAttributes: Boolean; virtual;
-    procedure Normalize;               // moved from TDOMElement
+    procedure Normalize;
 
 
     (*
     (*
     // TODO: What is that Java NULL for strings ???
     // TODO: What is that Java NULL for strings ???
@@ -248,10 +249,12 @@ type
     property Prefix: DOMString read FPrefix (write SetPrefix?);
     property Prefix: DOMString read FPrefix (write SetPrefix?);
     property LocalName: DOMString read FLocalName;
     property LocalName: DOMString read FLocalName;
     *)
     *)
+    // DOM level 3
     property TextContent: DOMString read GetTextContent write SetTextContent;
     property TextContent: DOMString read GetTextContent write SetTextContent;
     // Extensions to DOM interface:
     // Extensions to DOM interface:
     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;
   end;
   end;
 
 
 
 
@@ -320,15 +323,17 @@ type
 //   NamedNodeMap
 //   NamedNodeMap
 // -------------------------------------------------------
 // -------------------------------------------------------
 
 
-  TDOMNamedNodeMap = class(TList)
+  TDOMNamedNodeMap = class(TObject)
   protected
   protected
-    // FIX: track ownership by element, in order to implement DOM2 Attr.OwnerElement
     FOwnerElement: TDOMNode;
     FOwnerElement: TDOMNode;
+    FNodeType: Integer;
+    FList: TList;
     function GetItem(index: LongWord): TDOMNode;
     function GetItem(index: LongWord): TDOMNode;
     function GetLength: LongWord;
     function GetLength: LongWord;
+    function Find(const name: DOMString; out Index: LongWord): Boolean;
+    function InternalRemove(const name: DOMString): TDOMNode;
   public
   public
-    // FIX: ownership; see above
-    constructor Create(AOwner: TDOMNode);
+    constructor Create(AOwner: TDOMNode; ANodeType: Integer);
     destructor Destroy; override;
     destructor Destroy; override;
 
 
     function GetNamedItem(const name: DOMString): TDOMNode;
     function GetNamedItem(const name: DOMString): TDOMNode;
@@ -339,8 +344,8 @@ type
     function setNamedItemNS(arg: TDOMNode): TDOMNode;
     function setNamedItemNS(arg: TDOMNode): TDOMNode;
     function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode;
     function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode;
 
 
-    // FIX: made readonly. Reason: Anyone was allowed to insert any node without any checking.  
-    property Item[index: LongWord]: TDOMNode read GetItem {write SetItem}; default;
+    // FIX: made readonly. Reason: Anyone was allowed to insert any node without any checking.
+    property Item[index: LongWord]: TDOMNode read GetItem; default;
     property Length: LongWord read GetLength;
     property Length: LongWord read GetLength;
   end;
   end;
 
 
@@ -474,6 +479,8 @@ type
     property Value: DOMString read GetNodeValue write SetNodeValue;
     property Value: DOMString read GetNodeValue write SetNodeValue;
     // Introduced in DOM level 2:
     // Introduced in DOM level 2:
     property OwnerElement: TDOMElement read FOwnerElement;
     property OwnerElement: TDOMElement read FOwnerElement;
+    // extensions
+    function CompareName(const AName: DOMString): Integer; override;
   end;
   end;
 
 
 
 
@@ -496,22 +503,23 @@ type
     procedure SetAttribute(const name, value: DOMString);
     procedure SetAttribute(const name, value: DOMString);
     procedure RemoveAttribute(const name: DOMString);
     procedure RemoveAttribute(const name: DOMString);
     function  GetAttributeNode(const name: DOMString): TDOMAttr;
     function  GetAttributeNode(const name: DOMString): TDOMAttr;
-    // FIX: Changed to a function, as per DOM 2
     function SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
     function SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
-    function  RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
+    function RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
     // Free NodeList with TDOMNodeList.Release!
     // Free NodeList with TDOMNodeList.Release!
     function  GetElementsByTagName(const name: DOMString): TDOMNodeList;
     function  GetElementsByTagName(const name: DOMString): TDOMNodeList;
 
 
     // Introduced in DOM Level 2:
     // Introduced in DOM Level 2:
     function GetAttributeNS(const namespaceURI, localName: DOMString): DOMString;
     function GetAttributeNS(const namespaceURI, localName: DOMString): DOMString;
-    procedure SetAttributeNS(const namespaceURI, qualifiedName, value: DOMString); // raises (DOMException)
-    procedure RemoveAttributeNS(const namespaceURI, localName: DOMString);         // raises(DOMException);
+    procedure SetAttributeNS(const namespaceURI, qualifiedName, value: DOMString);
+    procedure RemoveAttributeNS(const namespaceURI, localName: DOMString);
     function GetAttributeNodeNS(const namespaceURI, localName: DOMString): TDOMAttr;
     function GetAttributeNodeNS(const namespaceURI, localName: DOMString): TDOMAttr;
-    function SetAttributeNodeNS(newAttr: TDOMAttr): TDOMAttr;                      // raises(DOMException);
+    function SetAttributeNodeNS(newAttr: TDOMAttr): TDOMAttr;
     function GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
     function GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
     function hasAttribute(const name: DOMString): Boolean;
     function hasAttribute(const name: DOMString): Boolean;
     function hasAttributeNS(const namespaceURI, localName: DOMString): Boolean;
     function hasAttributeNS(const namespaceURI, localName: DOMString): Boolean;
     function HasAttributes: Boolean; override;
     function HasAttributes: Boolean; override;
+    // extension
+    function CompareName(const name: DOMString): Integer; override;
 
 
     property AttribStrings[const Name: DOMString]: DOMString
     property AttribStrings[const Name: DOMString]: DOMString
       read GetAttribute write SetAttribute; default;
       read GetAttribute write SetAttribute; default;
@@ -629,6 +637,8 @@ type
     FName: DOMString;
     FName: DOMString;
     function GetNodeType: Integer; override;
     function GetNodeType: Integer; override;
     function GetNodeName: DOMString; override;
     function GetNodeName: DOMString; override;
+  public
+    function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
   end;
   end;
 
 
 
 
@@ -756,6 +766,13 @@ begin
   inherited Create;
   inherited Create;
 end;
 end;
 
 
+destructor TDOMNode.Destroy;
+begin
+  if Assigned(FParentNode) and FParentNode.InheritsFrom(TDOMNode_WithChildren) then
+    TDOMNode_WithChildren(FParentNode).DoRemoveChild(Self);
+  inherited Destroy;
+end;
+
 function TDOMNode.GetNodeName: DOMString;
 function TDOMNode.GetNodeName: DOMString;
 begin
 begin
   Result := '';
   Result := '';
@@ -805,7 +822,7 @@ end;
 
 
 function TDOMNode.RemoveChild(OldChild: TDOMNode): TDOMNode;
 function TDOMNode.RemoveChild(OldChild: TDOMNode): TDOMNode;
 begin
 begin
-  // OldChild isn't in our child list 
+  // OldChild isn't in our child list
   raise EDOMNotFound.Create('Node.RemoveChild');
   raise EDOMNotFound.Create('Node.RemoveChild');
   Result:=nil;
   Result:=nil;
 end;
 end;
@@ -833,19 +850,8 @@ begin
 end;
 end;
 
 
 function TDOMNode.FindNode(const ANodeName: DOMString): TDOMNode;
 function TDOMNode.FindNode(const ANodeName: DOMString): TDOMNode;
-var
-  child: TDOMNode;
 begin
 begin
-  child := FirstChild;
-  while Assigned(child) do
-  begin
-    if child.NodeName = ANodeName then
-    begin
-      Result := child;
-      exit;
-    end;
-    child := child.NextSibling;
-  end;
+  // FIX: we have no children, hence cannot find anything
   Result := nil;
   Result := nil;
 end;
 end;
 
 
@@ -904,8 +910,6 @@ begin
   NodeValue := AValue;
   NodeValue := AValue;
 end;
 end;
 
 
-//------------------------------------------------------------------------------
-
 function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
 function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
 var i: integer;
 var i: integer;
 begin
 begin
@@ -917,6 +921,18 @@ begin
   end;
   end;
 end;
 end;
 
 
+// generic version (slow)
+function TDOMNode.CompareName(const name: DOMString): Integer;
+var
+  SelfName: DOMString;
+begin
+  SelfName := NodeName;
+  Result := CompareDOMStrings(DOMPChar(name), DOMPChar(SelfName), Length(name), Length(SelfName));
+end;
+
+
+//------------------------------------------------------------------------------
+
 function CompareDOMNodeWithDOMNode(Node1, Node2: Pointer): integer;
 function CompareDOMNodeWithDOMNode(Node1, Node2: Pointer): integer;
 begin
 begin
   Result:=CompareDOMStrings(DOMPChar(TDOMNode(Node1).NodeName),
   Result:=CompareDOMStrings(DOMPChar(TDOMNode(Node1).NodeName),
@@ -928,11 +944,7 @@ end;
 
 
 function CompareDOMStringWithDOMNode(AKey, ANode: Pointer): integer;
 function CompareDOMStringWithDOMNode(AKey, ANode: Pointer): integer;
 begin
 begin
-  Result:=CompareDOMStrings(DOMPChar(AKey),
-                            DOMPChar(TDOMNode(ANode).NodeName),
-                            length(DOMString(AKey)),
-                            length(TDOMNode(ANode).NodeName)
-                            );
+  Result := TDOMNode(ANode).CompareName(DOMString(AKey));
 end;
 end;
 
 
 
 
@@ -948,10 +960,7 @@ end;
 
 
 destructor TDOMNode_WithChildren.Destroy;
 destructor TDOMNode_WithChildren.Destroy;
 begin
 begin
-  if FChildNodeTree<>nil then begin
-    FChildNodeTree.Free;
-    FChildNodeTree:=nil;
-  end;
+  FreeAndNil(FChildNodeTree);
   FreeChildren;
   FreeChildren;
   inherited Destroy;
   inherited Destroy;
 end;
 end;
@@ -979,7 +988,7 @@ begin
 
 
   // ugly workaround for RemoveChild issue... 
   // ugly workaround for RemoveChild issue... 
   if Assigned(NewChild.FParentNode) then
   if Assigned(NewChild.FParentNode) then
-    if NewChild.FParentNode.ClassType = TDOMNode_WithChildren then
+    if NewChild.FParentNode.InheritsFrom(TDOMNode_WithChildren) then
       TDOMNode_WithChildren(NewChild.FParentNode).DoRemoveChild(NewChild);
       TDOMNode_WithChildren(NewChild.FParentNode).DoRemoveChild(NewChild);
 
 
   // DONE: Implemented InsertBefore for DocumentFragments (except ChildNodeTree)
   // DONE: Implemented InsertBefore for DocumentFragments (except ChildNodeTree)
@@ -999,6 +1008,7 @@ begin
     // won't get here if RefChild = nil...
     // won't get here if RefChild = nil...
     if (RefChild = nil) or (RefChild.FPreviousSibling = nil) then
     if (RefChild = nil) or (RefChild.FPreviousSibling = nil) then
     begin  // insert at the beginning  <- AppendChild ??? ->
     begin  // insert at the beginning  <- AppendChild ??? ->
+      // no, AppendChild will insert after RefChild and we need it before
       if Assigned(FirstChild) then
       if Assigned(FirstChild) then
         FirstChild.FPreviousSibling := NewChild.LastChild;
         FirstChild.FPreviousSibling := NewChild.LastChild;
       NewChild.LastChild.FNextSibling := FirstChild;
       NewChild.LastChild.FNextSibling := FirstChild;
@@ -1102,7 +1112,7 @@ begin
   // TODO: RemoveChild destroys removed node -> CRASH
   // TODO: RemoveChild destroys removed node -> CRASH
   // this is a very ugly workaround...
   // this is a very ugly workaround...
   if Assigned(NewChild.FParentNode) then
   if Assigned(NewChild.FParentNode) then
-    if NewChild.FParentNode.ClassType = TDOMNode_WithChildren then
+    if NewChild.FParentNode.InheritsFrom(TDOMNode_WithChildren) then
       TDOMNode_WithChildren(NewChild.FParentNode).DoRemoveChild(NewChild);
       TDOMNode_WithChildren(NewChild.FParentNode).DoRemoveChild(NewChild);
 
 
   // DONE: supported AppendChild for DocumentFragments (except ChildNodeTree)
   // DONE: supported AppendChild for DocumentFragments (except ChildNodeTree)
@@ -1186,9 +1196,12 @@ begin
   while Assigned(child) do
   while Assigned(child) do
   begin
   begin
     next := child.NextSibling;
     next := child.NextSibling;
+    child.FParentNode := nil;
     child.Free;
     child.Free;
     child := next;
     child := next;
   end;
   end;
+  FFirstChild := nil;
+  FLastChild := nil;
 end;
 end;
 
 
 function TDOMNode_WithChildren.GetTextContent: DOMString;
 function TDOMNode_WithChildren.GetTextContent: DOMString;
@@ -1333,43 +1346,69 @@ end;
 //   NamedNodeMap
 //   NamedNodeMap
 // -------------------------------------------------------
 // -------------------------------------------------------
 
 
-constructor TDOMNamedNodeMap.Create(AOwner: TDOMNode);
+constructor TDOMNamedNodeMap.Create(AOwner: TDOMNode; ANodeType: Integer);
 begin
 begin
   inherited Create;
   inherited Create;
   FOwnerElement := AOwner;
   FOwnerElement := AOwner;
+  FNodeType := ANodeType;
+  FList := TList.Create;
 end;
 end;
 
 
-// TODO: should this be in overriden Clear()?
 destructor TDOMNamedNodeMap.Destroy;
 destructor TDOMNamedNodeMap.Destroy;
 var
 var
   I: Integer;
   I: Integer;
 begin
 begin
-  for I := Count-1 downto 0 do
-    Item[I].Free;
+  for I := FList.Count-1 downto 0 do
+    TDOMNode(FList[I]).Free;
+  FList.Free;
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
 function TDOMNamedNodeMap.GetItem(index: LongWord): TDOMNode;
 function TDOMNamedNodeMap.GetItem(index: LongWord): TDOMNode;
 begin
 begin
-  Result := TDOMNode(Items[index]);
+  if index < LongWord(FList.Count) then
+    Result := TDOMNode(FList.List^[index])
+  else
+    Result := nil;
 end;
 end;
 
 
 function TDOMNamedNodeMap.GetLength: LongWord;
 function TDOMNamedNodeMap.GetLength: LongWord;
 begin
 begin
-  Result := Count;
+  Result := FList.Count;
 end;
 end;
 
 
-function TDOMNamedNodeMap.GetNamedItem(const name: DOMString): TDOMNode;
+function TDOMNamedNodeMap.Find(const name: DOMString; out Index: LongWord): Boolean;
 var
 var
-  i: Integer;
+  L, H, I, C: Integer;
 begin
 begin
-  for i := 0 to Count - 1 do
+  Result := False;
+  L := 0;
+  H := FList.Count - 1;
+  while L <= H do
   begin
   begin
-    Result := Item[i];
-    if Result.NodeName = name then
-      exit;
+    I := (L + H) shr 1;
+    C := TDOMNode(FList.List^[I]).CompareName(name);
+    if C > 0 then L := I + 1 else
+    begin
+      H := I - 1;
+      if C = 0 then
+      begin
+        Result := True;
+        L := I;
+      end;
+    end;
   end;
   end;
-  Result := nil;
+  Index := L;
+end;
+
+function TDOMNamedNodeMap.GetNamedItem(const name: DOMString): TDOMNode;
+var
+  i: Cardinal;
+begin
+  if Find(name, i) then
+    Result := TDOMNode(FList.List^[i])
+  else
+    Result := nil;
 end;
 end;
 
 
 function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
@@ -1381,30 +1420,34 @@ end;
 
 
 function TDOMNamedNodeMap.SetNamedItem(arg: TDOMNode): TDOMNode;
 function TDOMNamedNodeMap.SetNamedItem(arg: TDOMNode): TDOMNode;
 var
 var
-  i: Integer;
+  i: Cardinal;
   AttrOwner: TDOMElement;
   AttrOwner: TDOMElement;
+  Exists: Boolean;
 begin
 begin
-  // FIX: attribute ownership
   if arg.FOwnerDocument <> FOwnerElement.FOwnerDocument then
   if arg.FOwnerDocument <> FOwnerElement.FOwnerDocument then
     raise EDOMWrongDocument.Create('NamedNodeMap.SetNamedItem');
     raise EDOMWrongDocument.Create('NamedNodeMap.SetNamedItem');
 
 
-  if arg.NodeType = ATTRIBUTE_NODE then
+  if arg.NodeType <> FNodeType then
+    raise EDOMHierarchyRequest.Create('NamedNodeMap.SetNamedItem');
+
+  if FNodeType = ATTRIBUTE_NODE then
   begin
   begin
     AttrOwner := TDOMAttr(arg).ownerElement;
     AttrOwner := TDOMAttr(arg).ownerElement;
-    // FIX: allow setting items which have the same owner
     if Assigned(AttrOwner) and (AttrOwner <> FOwnerElement) then
     if Assigned(AttrOwner) and (AttrOwner <> FOwnerElement) then
       raise EDOMInUseAttribute.Create('NamedNodeMap.SetNamedItem');
       raise EDOMInUseAttribute.Create('NamedNodeMap.SetNamedItem');
     TDOMAttr(arg).FOwnerElement := TDOMElement(FOwnerElement);
     TDOMAttr(arg).FOwnerElement := TDOMElement(FOwnerElement);
-  end;
+    Exists := Find(TDOMAttr(arg).FName, i); // optimization
+  end
+  else
+    Exists := Find(arg.NodeName, i);
 
 
-  for i := 0 to Count - 1 do
-    if Item[i].NodeName = arg.NodeName then
-    begin
-      Result := Item[i];
-      Items[i] := arg;
-      exit;
-    end;
-  Add(arg);
+  if Exists then
+  begin
+    Result := TDOMNode(FList.List^[i]);
+    FList.List^[i] := arg;
+    exit;
+  end;
+  FList.Insert(i, arg);
   Result := nil;
   Result := nil;
 end;
 end;
 
 
@@ -1414,23 +1457,25 @@ begin
   Result := nil;
   Result := nil;
 end;
 end;
 
 
-function TDOMNamedNodeMap.RemoveNamedItem(const name: DOMString): TDOMNode;
+function TDOMNamedNodeMap.InternalRemove(const name: DOMString): TDOMNode;
 var
 var
-  i: Integer;
+  i: Cardinal;
 begin
 begin
-  for i := 0 to Count - 1 do
-    if Item[i].NodeName = name then
-    begin
-      Result := Item[i];
-      // DONE: delete item from list
-      Delete(I);
-      // DONE: attribute ownership
-      if Result.NodeType = ATTRIBUTE_NODE then
-        TDOMAttr(Result).FOwnerElement := nil;
-      Result.FParentNode := nil;  // ??? should it ever be assigned for Attrs, Notations or Entities?
-      exit;
-    end;
-  raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItem');
+  Result := nil;
+  if Find(name, i) then
+  begin
+    Result := TDOMNode(FList.List^[i]);
+    FList.Delete(I);
+    if Result.NodeType = ATTRIBUTE_NODE then
+      TDOMAttr(Result).FOwnerElement := nil;
+  end;
+end;
+
+function TDOMNamedNodeMap.RemoveNamedItem(const name: DOMString): TDOMNode;
+begin
+  Result := InternalRemove(name);
+  if Result = nil then
+    raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItem');
 end;
 end;
 
 
 function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
@@ -1571,7 +1616,7 @@ end;
 constructor TDOMDocument.Create;
 constructor TDOMDocument.Create;
 begin
 begin
   inherited Create(nil);
   inherited Create(nil);
-  // TODO: DOM lvl 2 states that Document should be unowned. Any dependencies?  
+  // TODO: DOM lvl 2 states that Document should be unowned. Any dependencies?
   FOwnerDocument := Self;
   FOwnerDocument := Self;
 end;
 end;
 
 
@@ -1590,16 +1635,9 @@ var
   node: TDOMNode;
   node: TDOMNode;
 begin
 begin
   node := FFirstChild;
   node := FFirstChild;
-  while Assigned(node) do
-  begin
-    if node.NodeType = ELEMENT_NODE then
-    begin
-      Result := TDOMElement(node);
-      exit;
-    end;
+  while Assigned(node) and (node.NodeType <> ELEMENT_NODE) do
     node := node.NextSibling;
     node := node.NextSibling;
-  end;
-  Result := nil;
+  Result := TDOMElement(node);
 end;
 end;
 
 
 function TDOMDocument.GetDocType: TDOMDocumentType;
 function TDOMDocument.GetDocType: TDOMDocumentType;
@@ -1607,28 +1645,23 @@ var
   node: TDOMNode;
   node: TDOMNode;
 begin
 begin
   node := FFirstChild;
   node := FFirstChild;
-  while Assigned(node) do
-  begin
-    if node.NodeType = DOCUMENT_TYPE_NODE then
-    begin
-      Result := TDOMDocumentType(node);
-      exit;
-    end;
+  while Assigned(node) and (node.NodeType <> DOCUMENT_TYPE_NODE) do
     node := node.NextSibling;
     node := node.NextSibling;
-  end;
-  Result := nil;
+  Result := TDOMDocumentType(node);
 end;
 end;
 
 
 function TDOMDocument.CreateElement(const tagName: DOMString): TDOMElement;
 function TDOMDocument.CreateElement(const tagName: DOMString): TDOMElement;
 begin
 begin
   Result := TDOMElement.Create(Self);
   Result := TDOMElement.Create(Self);
   Result.FNodeName := tagName;
   Result.FNodeName := tagName;
+  // TODO: attach default attributes
 end;
 end;
 
 
 function TDOMDocument.CreateElementBuf(Buf: DOMPChar; Length: Integer): TDOMElement;
 function TDOMDocument.CreateElementBuf(Buf: DOMPChar; Length: Integer): TDOMElement;
 begin
 begin
   Result := TDOMElement.Create(Self);
   Result := TDOMElement.Create(Self);
   SetString(Result.FNodeName, Buf, Length);
   SetString(Result.FNodeName, Buf, Length);
+  // TODO: attach default attributes
 end;
 end;
 
 
 function TDOMDocument.CreateDocumentFragment: TDOMDocumentFragment;
 function TDOMDocument.CreateDocumentFragment: TDOMDocumentFragment;
@@ -1786,23 +1819,9 @@ end;
 
 
 function TDOMAttr.GetNodeValue: DOMString;
 function TDOMAttr.GetNodeValue: DOMString;
 var
 var
-  child: TDOMNode;
   I,J: Integer;
   I,J: Integer;
 begin
 begin
-  SetLength(Result, 0);
-  if Assigned(FFirstChild) then
-  begin
-    child := FFirstChild;
-    while Assigned(child) do
-    begin
-      if child.NodeType = ENTITY_REFERENCE_NODE then
-      // TODO: here we must substitute entity value
-        Result := Result + '&' + child.NodeName + ';'
-      else
-        Result := Result + child.NodeValue;
-      child := child.NextSibling;
-    end;
-  end;
+  Result := GetTextContent;
   // TODO: probably must be speed optimized
   // TODO: probably must be speed optimized
   if FNormalize then
   if FNormalize then
   begin
   begin
@@ -1827,6 +1846,10 @@ begin
   SetTextContent(AValue);
   SetTextContent(AValue);
 end;
 end;
 
 
+function TDOMAttr.CompareName(const AName: DOMString): Integer;
+begin
+  Result := CompareDOMStrings(DOMPChar(AName), DOMPChar(FName), Length(AName), Length(FName));
+end;
 
 
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   Element
 //   Element
@@ -1854,11 +1877,10 @@ var
   i: Integer;
   i: Integer;
 begin
 begin
   Result := ACloneOwner.CreateElement(FNodeName);
   Result := ACloneOwner.CreateElement(FNodeName);
-  if FAttributes<>nil then
+  if Assigned(FAttributes) then
   begin
   begin
-    TDOMElement(Result).GetAttributes;
-    for i := 0 to FAttributes.Count - 1 do
-      TDOMElement(Result).FAttributes.Add(FAttributes[i].CloneNode(True, ACloneOwner));
+    for i := 0 to FAttributes.Length - 1 do
+      TDOMElement(Result).SetAttributeNode(TDOMAttr(FAttributes[i].CloneNode(True, ACloneOwner)));
   end;
   end;
   if deep then
   if deep then
     CloneChildren(Result, ACloneOwner);
     CloneChildren(Result, ACloneOwner);
@@ -1867,8 +1889,7 @@ end;
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 begin
 begin
   if FAttributes=nil then
   if FAttributes=nil then
-    // FIX: ownership
-    FAttributes := TDOMNamedNodeMap.Create(Self);
+    FAttributes := TDOMNamedNodeMap.Create(Self, ATTRIBUTE_NODE);
   Result := FAttributes;
   Result := FAttributes;
 end;
 end;
 
 
@@ -1876,7 +1897,6 @@ function TDOMElement.GetAttribute(const name: DOMString): DOMString;
 var
 var
   Attr: TDOMNode;
   Attr: TDOMNode;
 begin
 begin
-  // DONE: delegated to TNamedNodeMap
   SetLength(Result, 0);
   SetLength(Result, 0);
   if Assigned(FAttributes) then
   if Assigned(FAttributes) then
   begin
   begin
@@ -1901,35 +1921,24 @@ end;
 
 
 procedure TDOMElement.SetAttribute(const name, value: DOMString);
 procedure TDOMElement.SetAttribute(const name, value: DOMString);
 var
 var
+  I: Cardinal;
   attr: TDOMAttr;
   attr: TDOMAttr;
 begin
 begin
-  GetAttributes;
-  // DONE: delegate to TNamedNodeMap
-  Attr := FAttributes.GetNamedItem(name) as TDOMAttr;
-  if Assigned(Attr) then
-    Attr.NodeValue := value
+  if Attributes.Find(name, I) then
+    Attr := FAttributes[I] as TDOMAttr
   else
   else
   begin
   begin
-    attr := FOwnerDocument.CreateAttribute(name);
-    attr.NodeValue := value;
-    FAttributes.Add(attr);
+    Attr := FOwnerDocument.CreateAttribute(name);
+    FAttributes.FList.Insert(I, Attr);
   end;
   end;
+  attr.NodeValue := value;
 end;
 end;
 
 
 procedure TDOMElement.RemoveAttribute(const name: DOMString);
 procedure TDOMElement.RemoveAttribute(const name: DOMString);
-var
-  i: Integer;
 begin
 begin
-  // (note) cannot call NamedNodeMap.RemoveNamedItem because it can raise NOT_FOUND_ERR
-  // and we should not raise it.
-  if FAttributes=nil then exit;
-  for i := 0 to FAttributes.Count - 1 do
-    if FAttributes[i].NodeName = name then
-    begin
-      FAttributes[i].Free;
-      FAttributes.Delete(i);
-      exit;
-    end;
+// (note) NamedNodeMap.RemoveNamedItem can raise NOT_FOUND_ERR and we should not.
+  if Assigned(FAttributes) then
+    FAttributes.InternalRemove(name).Free;
 end;
 end;
 
 
 procedure TDOMElement.RemoveAttributeNS(const namespaceURI,
 procedure TDOMElement.RemoveAttributeNS(const namespaceURI,
@@ -1944,16 +1953,14 @@ procedure TDOMElement.SetAttributeNS(const namespaceURI, qualifiedName,
 var
 var
   Attr: TDOMAttr;
   Attr: TDOMAttr;
 begin
 begin
-  GetAttributes;
-  Attr := FAttributes.GetNamedItemNS(namespaceURI, qualifiedName) as TDOMAttr;
-  if Assigned(Attr) then
-    Attr.NodeValue := value
-  else
+  Attr := Attributes.GetNamedItemNS(namespaceURI, qualifiedName) as TDOMAttr;
+  if attr = nil then
   begin
   begin
     attr := FOwnerDocument.CreateAttributeNS(namespaceURI, qualifiedName);
     attr := FOwnerDocument.CreateAttributeNS(namespaceURI, qualifiedName);
-    attr.NodeValue := value;
-    FAttributes.Add(attr);
+    // TODO 5: keep sorted!
+    FAttributes.FList.Add(attr);
   end;
   end;
+  attr.NodeValue := value;
 end;
 end;
 
 
 function TDOMElement.GetAttributeNode(const name: DOMString): TDOMAttr;
 function TDOMElement.GetAttributeNode(const name: DOMString): TDOMAttr;
@@ -1975,11 +1982,7 @@ end;
 
 
 function TDOMElement.SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
 function TDOMElement.SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
 begin
 begin
-  // FIX #1: FAttributes must be created if none
-  // FIX #2: if no such attribute present, it should be added
-  // FIX #3: All delegated to TDOMNamedNodeMap
-  GetAttributes;
-  Result := FAttributes.SetNamedItem(NewAttr) as TDOMAttr;
+  Result := Attributes.SetNamedItem(NewAttr) as TDOMAttr;
 
 
   // TODO -cConformance: here goes inconsistency with DOM 2 - same as in TDOMNode.RemoveChild
   // TODO -cConformance: here goes inconsistency with DOM 2 - same as in TDOMNode.RemoveChild
   Result.Free;
   Result.Free;
@@ -1988,8 +1991,7 @@ end;
 
 
 function TDOMElement.SetAttributeNodeNS(NewAttr: TDOMAttr): TDOMAttr;
 function TDOMElement.SetAttributeNodeNS(NewAttr: TDOMAttr): TDOMAttr;
 begin
 begin
-  GetAttributes;
-  Result := FAttributes.SetNamedItemNS(NewAttr) as TDOMAttr;
+  Result := Attributes.SetNamedItemNS(NewAttr) as TDOMAttr;
 
 
   // TODO -cConformance: here goes inconsistency with DOM 2 - same as in TDOMNode.RemoveChild
   // TODO -cConformance: here goes inconsistency with DOM 2 - same as in TDOMNode.RemoveChild
   Result.Free;
   Result.Free;
@@ -2005,7 +2007,7 @@ begin
   //       -- but what is the purpose of return value then?
   //       -- but what is the purpose of return value then?
   // TODO: delegate to TNamedNodeMap?  Nope, it does not have such method
   // TODO: delegate to TNamedNodeMap?  Nope, it does not have such method
   // (note) one way around is to remove by name
   // (note) one way around is to remove by name
-  if FAttributes.Remove(OldAttr) > -1 then
+  if FAttributes.FList.Remove(OldAttr) > -1 then
     Result := OldAttr;
     Result := OldAttr;
 end;
 end;
 
 
@@ -2033,7 +2035,12 @@ end;
 
 
 function TDOMElement.HasAttributes: Boolean;
 function TDOMElement.HasAttributes: Boolean;
 begin
 begin
-  Result := Assigned(FAttributes) and (FAttributes.Count > 0);
+  Result := Assigned(FAttributes) and (FAttributes.Length > 0);
+end;
+
+function TDOMElement.CompareName(const Name: DOMString): Integer;
+begin
+  Result := CompareDOMStrings(DOMPChar(name), DOMPChar(FNodeName), Length(name), Length(FNodeName));
 end;
 end;
 
 
 // -------------------------------------------------------
 // -------------------------------------------------------
@@ -2052,8 +2059,7 @@ end;
 
 
 function TDOMText.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 function TDOMText.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 begin
 begin
-  Result := ACloneOwner.CreateTextNode(FNodeValue); {Data?}
-  // ignore deep because text cannot have children
+  Result := ACloneOwner.CreateTextNode(FNodeValue);
 end;
 end;
 
 
 function TDOMText.SplitText(offset: LongWord): TDOMText;
 function TDOMText.SplitText(offset: LongWord): TDOMText;
@@ -2142,14 +2148,14 @@ end;
 function TDOMDocumentType.GetEntities: TDOMNamedNodeMap;
 function TDOMDocumentType.GetEntities: TDOMNamedNodeMap;
 begin
 begin
   if FEntities = nil then
   if FEntities = nil then
-    FEntities := TDOMNamedNodeMap.Create(Self);
+    FEntities := TDOMNamedNodeMap.Create(Self, ENTITY_NODE);
   Result := FEntities;
   Result := FEntities;
 end;
 end;
 
 
 function TDOMDocumentType.GetNotations: TDOMNamedNodeMap;
 function TDOMDocumentType.GetNotations: TDOMNamedNodeMap;
 begin
 begin
   if FNotations = nil then
   if FNotations = nil then
-    FNotations := TDOMNamedNodeMap.Create(Self);
+    FNotations := TDOMNamedNodeMap.Create(Self, NOTATION_NODE);
   Result := FNotations;
   Result := FNotations;
 end;
 end;
 
 
@@ -2205,6 +2211,12 @@ begin
   Result := FName;
   Result := FName;
 end;
 end;
 
 
+function TDOMEntityReference.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
+begin
+  Result := ACloneOwner.CreateEntityReference(FName);
+  CloneChildren(Result, ACloneOwner);
+end;
+
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   ProcessingInstruction
 //   ProcessingInstruction
 // -------------------------------------------------------
 // -------------------------------------------------------