Browse Source

+ Part of DOM level 2 namespace support (handling namespaced attributes)
* Cloning/importing attributes and elements preserves their namespaces
* Importing an element does not import non-specified attributes.
+ Separate, much faster, implementation for element cloning.

git-svn-id: trunk@13226 -

sergei 16 years ago
parent
commit
2cd0a1ad27
1 changed files with 193 additions and 34 deletions
  1. 193 34
      packages/fcl-xml/src/dom.pp

+ 193 - 34
packages/fcl-xml/src/dom.pp

@@ -344,11 +344,10 @@ type
     function SetNamedItem(arg: TDOMNode): TDOMNode;
     function SetNamedItem(arg: TDOMNode): TDOMNode;
     function RemoveNamedItem(const name: DOMString): TDOMNode;
     function RemoveNamedItem(const name: DOMString): TDOMNode;
     // Introduced in DOM Level 2:
     // Introduced in DOM Level 2:
-    function getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
-    function setNamedItemNS(arg: TDOMNode): TDOMNode;
-    function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode;
+    function getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode; virtual;
+    function setNamedItemNS(arg: TDOMNode): TDOMNode; virtual;
+    function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode; virtual;
 
 
-    // FIX: made readonly. Reason: Anyone was allowed to insert any node without any checking.
     property Item[index: LongWord]: TDOMNode read GetItem; default;
     property Item[index: LongWord]: TDOMNode read GetItem; default;
     property Length: LongWord read GetLength;
     property Length: LongWord read GetLength;
   end;
   end;
@@ -788,6 +787,19 @@ const
 
 
 implementation
 implementation
 
 
+{ a namespace-enabled NamedNodeMap }
+type
+  TAttributeMap = class(TDOMNamedNodeMap)
+  private
+    function FindNS(nsIndex: Integer; const aLocalName: DOMString;
+      out Index: LongWord): Boolean;
+    function InternalRemoveNS(const nsURI, aLocalName: DOMString): TDOMNode;
+  public
+    function getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode; override;
+    function setNamedItemNS(arg: TDOMNode): TDOMNode; override;
+    function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode; override;
+  end;
+
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   DOM Exception
 //   DOM Exception
 // -------------------------------------------------------
 // -------------------------------------------------------
@@ -1551,11 +1563,11 @@ begin
     Result := nil;
     Result := nil;
 end;
 end;
 
 
+// Note: this *may* raise NOT_SUPPORTED_ERR if the document is e.g. HTML.
+// This isn't checked now.
 function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 begin
 begin
-  // TODO: implement TDOMNamedNodeMap.GetNamedItemNS
-  raise EDOMNotSupported.Create('TDOMNamedNodeMap.GetNamedItemNS');
-    Result := nil;
+  Result := nil;
 end;
 end;
 
 
 function TDOMNamedNodeMap.ValidateInsert(arg: TDOMNode): Integer;
 function TDOMNamedNodeMap.ValidateInsert(arg: TDOMNode): Integer;
@@ -1608,15 +1620,13 @@ begin
 end;
 end;
 
 
 function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
 function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
-var
-  res: Integer;
 begin
 begin
-  // TODO: implement TDOMNamedNodeMap.SetNamedItemNS
-  res := ValidateInsert(arg);
-  if res <> 0 then
-    raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItemNS');
-
-    Result := nil;
+{ Since the map contains only namespaceless nodes (all having empty
+  localName and namespaceURI properties), a namespaced arg won't match
+  any of them. Therefore, add it using nodeName as key.
+  Note: a namespaceless arg is another story, as it will match *any* node
+  in the map. This can be considered as a flaw in specs. }
+  Result := SetNamedItem(arg);
 end;
 end;
 
 
 function TDOMNamedNodeMap.Delete(index: LongWord): TDOMNode;
 function TDOMNamedNodeMap.Delete(index: LongWord): TDOMNode;
@@ -1670,12 +1680,112 @@ end;
 
 
 function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 begin
 begin
-  if nfReadOnly in FOwner.FFlags then
-    raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItemNS');
-  // TODO: Implement TDOMNamedNodeMap.RemoveNamedItemNS
+// see comments to SetNamedItemNS. Related tests are written clever enough
+// in the sense they don't expect NO_MODIFICATION_ERR in first place.
+  raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItemNS');
+  Result := nil;
+end;
+
+{ TAttributeMap }
+
+// Since list is kept sorted by nodeName, we must use linear search here.
+// This routine is not called while parsing, so parsing speed is not lowered.
+function TAttributeMap.FindNS(nsIndex: Integer; const aLocalName: DOMString;
+  out Index: LongWord): Boolean;
+var
+  I: Integer;
+  P: DOMPChar;
+begin
+  for I := 0 to FList.Count-1 do
+  begin
+    with TDOMAttr(FList.List^[I]) do
+    begin
+      if nsIndex = FNSI.NSIndex then
+      begin
+        P := DOMPChar(FNSI.QName^.Key);
+        if FNSI.PrefixLen > 1 then
+          Inc(P, FNSI.PrefixLen);
+        if CompareDOMStrings(DOMPChar(aLocalName), P, System.Length(aLocalName), System.Length(FNSI.QName^.Key) - FNSI.PrefixLen) = 0 then
+        begin
+          Index := I;
+          Result := True;
+          Exit;
+        end;
+      end;
+    end;
+  end;
+  Result := False;
+end;
+
+function TAttributeMap.InternalRemoveNS(const nsURI, aLocalName: DOMString): TDOMNode;
+var
+  i: Cardinal;
+  nsIndex: Integer;
+begin
+  Result := nil;
+  nsIndex := FOwner.FOwnerDocument.IndexOfNS(nsURI);
+  if (nsIndex >= 0) and FindNS(nsIndex, aLocalName, i) then
+  begin
+    Result := Delete(I);
+    RestoreDefault(TDOMAttr(Result).FNSI.QName^.Key);
+  end;
+end;
+
+function TAttributeMap.getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
+var
+  nsIndex: Integer;
+  i: LongWord;
+begin
+  nsIndex := FOwner.FOwnerDocument.IndexOfNS(namespaceURI);
+  if (nsIndex >= 0) and FindNS(nsIndex, localName, i) then
+    Result := TDOMNode(FList.List^[i])
+  else
+    Result := nil;
+end;
+
+function TAttributeMap.setNamedItemNS(arg: TDOMNode): TDOMNode;
+var
+  i: LongWord;
+  res: Integer;
+  Exists: Boolean;
+begin
+  res := ValidateInsert(arg);
+  if res <> 0 then
+    raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItemNS');
+
   Result := nil;
   Result := nil;
+  with TDOMAttr(arg) do
+  begin
+    // calling LocalName is no good... but it is done once
+    if FindNS(FNSI.NSIndex, localName, i) then
+    begin
+      Result := TDOMNode(FList.List^[i]);
+      FList.Delete(i);
+    end;
+    // Do a non-namespace search in order to keep the list sorted on nodeName
+    Exists := Find(FNSI.QName^.Key, i);
+    if Exists and (Result = nil) then  // case when arg has no namespace
+    begin
+      Result := TDOMNode(FList.List^[i]);
+      FList.List^[i] := arg;
+    end
+    else
+      FList.Insert(i, arg);
+  end;
+  if Assigned(Result) then
+    TDOMAttr(Result).FOwnerElement := nil;
+  TDOMAttr(arg).FOwnerElement := TDOMElement(FOwner);
 end;
 end;
 
 
+function TAttributeMap.removeNamedItemNS(const namespaceURI,
+  localName: DOMString): TDOMNode;
+begin
+  if nfReadOnly in FOwner.FFlags then
+    raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItemNS');
+  Result := InternalRemoveNS(namespaceURI, localName);
+  if Result = nil then
+     raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItemNS');
+end;
 
 
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   CharacterData
 //   CharacterData
@@ -2353,7 +2463,10 @@ end;
 function TDOMAttr.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 function TDOMAttr.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 begin
 begin
   // Cloned attribute is always specified and carries its children
   // Cloned attribute is always specified and carries its children
-  Result := ACloneOwner.CreateAttribute(NodeName);
+  if nfLevel2 in FFlags then
+    Result := ACloneOwner.CreateAttributeNS(namespaceURI, NodeName)
+  else
+    Result := ACloneOwner.CreateAttribute(NodeName);
   TDOMAttr(Result).FDataType := FDataType;
   TDOMAttr(Result).FDataType := FDataType;
   CloneChildren(Result, ACloneOwner);
   CloneChildren(Result, ACloneOwner);
 end;
 end;
@@ -2390,7 +2503,6 @@ begin
   Include(FFlags, nfDestroying);
   Include(FFlags, nfDestroying);
   if Assigned(FOwnerDocument.FIDList) then
   if Assigned(FOwnerDocument.FIDList) then
     FOwnerDocument.RemoveID(Self);
     FOwnerDocument.RemoveID(Self);
-  // FIX: Attribute nodes are now freed by TDOMNamedNodeMap.Destroy
   FreeAndNil(FAttributes);
   FreeAndNil(FAttributes);
   inherited Destroy;
   inherited Destroy;
 end;
 end;
@@ -2398,12 +2510,45 @@ end;
 function TDOMElement.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 function TDOMElement.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 var
 var
   i: Integer;
   i: Integer;
+  Attr, AttrClone: TDOMAttr;
 begin
 begin
-  Result := ACloneOwner.CreateElement(NodeName);
-  if Assigned(FAttributes) then
+  if ACloneOwner <> FOwnerDocument then
+  begin
+    // Importing has to go the hard way...
+    if nfLevel2 in FFlags then
+      Result := ACloneOwner.CreateElementNS(NamespaceURI, NodeName)
+    else
+      Result := ACloneOwner.CreateElement(NodeName);
+    if Assigned(FAttributes) then
+    begin
+      for i := 0 to FAttributes.Length - 1 do
+      begin
+        Attr := TDOMAttr(FAttributes[i]);
+        // destroy defaulted attributes (if any), it is safe because caller had not seen them yet
+        if Attr.Specified then
+          TDOMElement(Result).SetAttributeNode(TDOMAttr(Attr.CloneNode(True, ACloneOwner))).Free;
+      end;
+    end;
+  end
+  else   // Cloning may cheat a little bit.
   begin
   begin
-    for i := 0 to FAttributes.Length - 1 do
-      TDOMElement(Result).SetAttributeNode(TDOMAttr(FAttributes[i].CloneNode(True, ACloneOwner)));
+    Result := FOwnerDocument.Alloc(TDOMElement);
+    TDOMElement(Result).Create(FOwnerDocument);
+    TDOMElement(Result).FNSI := FNSI;
+    if nfLevel2 in FFlags then
+      Include(Result.FFlags, nfLevel2);
+    if Assigned(FAttributes) then
+    begin
+      // clone all attributes, but preserve nfSpecified flag
+      for i := 0 to FAttributes.Length - 1 do
+      begin
+        Attr := TDOMAttr(FAttributes[i]);
+        AttrClone := TDOMAttr(Attr.CloneNode(True, ACloneOwner));
+        if not Attr.Specified then
+          Exclude(AttrClone.FFlags, nfSpecified);
+        TDOMElement(Result).SetAttributeNode(AttrClone);
+      end;
+    end;
   end;
   end;
   if deep then
   if deep then
     CloneChildren(Result, ACloneOwner);
     CloneChildren(Result, ACloneOwner);
@@ -2450,7 +2595,7 @@ end;
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 begin
 begin
   if FAttributes=nil then
   if FAttributes=nil then
-    FAttributes := TDOMNamedNodeMap.Create(Self, ATTRIBUTE_NODE);
+    FAttributes := TAttributeMap.Create(Self, ATTRIBUTE_NODE);
   Result := FAttributes;
   Result := FAttributes;
 end;
 end;
 
 
@@ -2509,13 +2654,14 @@ procedure TDOMElement.RemoveAttributeNS(const nsURI,
   aLocalName: DOMString);
   aLocalName: DOMString);
 begin
 begin
   Changing;
   Changing;
-  // TODO: Implement TDOMElement.RemoveAttributeNS
-  raise EDOMNotSupported.Create('TDOMElement.RemoveAttributeNS');
+  if Assigned(FAttributes) then
+    TAttributeMap(FAttributes).InternalRemoveNS(nsURI, aLocalName).Free;
 end;
 end;
 
 
 procedure TDOMElement.SetAttributeNS(const nsURI, qualifiedName,
 procedure TDOMElement.SetAttributeNS(const nsURI, qualifiedName,
   value: DOMString);
   value: DOMString);
 var
 var
+  I: Cardinal;
   Attr: TDOMAttr;
   Attr: TDOMAttr;
   idx, prefIdx: Integer;
   idx, prefIdx: Integer;
 begin
 begin
@@ -2524,14 +2670,27 @@ begin
   prefIdx := CheckQName(qualifiedName, idx, FOwnerDocument.FXml11);
   prefIdx := CheckQName(qualifiedName, idx, FOwnerDocument.FXml11);
   if prefIdx < 0 then
   if prefIdx < 0 then
     raise EDOMError.Create(-prefIdx, 'Element.SetAttributeNS');
     raise EDOMError.Create(-prefIdx, 'Element.SetAttributeNS');
-    
-  Attr := Attributes.GetNamedItemNS(nsURI, qualifiedName) as TDOMAttr;
-  if attr = nil then
+
+  if TAttributeMap(Attributes).FindNS(idx, Copy(qualifiedName, prefIdx+1, MaxInt), I) then
   begin
   begin
-    attr := FOwnerDocument.CreateAttributeNS(nsURI, qualifiedName);
-    // TODO 5: keep sorted!
-    FAttributes.FList.Add(attr);
-  end;
+    Attr := TDOMAttr(FAttributes[I]);
+    // need to reinsert because the nodeName may change
+    FAttributes.FList.Delete(I);
+  end
+  else
+  begin
+    TDOMNode(Attr) := FOwnerDocument.Alloc(TDOMAttr);
+    Attr.Create(FOwnerDocument);
+    Attr.FOwnerElement := Self;
+    Attr.FNSI.NSIndex := Word(idx);
+    Include(Attr.FFlags, nfLevel2);
+  end;
+  // keep list sorted by DOM Level 1 name
+  FAttributes.Find(qualifiedName, I);
+  FAttributes.FList.Insert(I, Attr);
+  // TODO: rehash properly, same issue as with Node.SetPrefix()
+  Attr.FNSI.QName := FOwnerDocument.FNames.FindOrAdd(DOMPChar(qualifiedName), Length(qualifiedName));
+  Attr.FNSI.PrefixLen := Word(prefIdx);
   attr.NodeValue := value;
   attr.NodeValue := value;
 end;
 end;