|
@@ -344,11 +344,10 @@ type
|
|
|
function SetNamedItem(arg: TDOMNode): TDOMNode;
|
|
|
function RemoveNamedItem(const name: DOMString): TDOMNode;
|
|
|
// 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 Length: LongWord read GetLength;
|
|
|
end;
|
|
@@ -788,6 +787,19 @@ const
|
|
|
|
|
|
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
|
|
|
// -------------------------------------------------------
|
|
@@ -1551,11 +1563,11 @@ begin
|
|
|
Result := nil;
|
|
|
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;
|
|
|
begin
|
|
|
- // TODO: implement TDOMNamedNodeMap.GetNamedItemNS
|
|
|
- raise EDOMNotSupported.Create('TDOMNamedNodeMap.GetNamedItemNS');
|
|
|
- Result := nil;
|
|
|
+ Result := nil;
|
|
|
end;
|
|
|
|
|
|
function TDOMNamedNodeMap.ValidateInsert(arg: TDOMNode): Integer;
|
|
@@ -1608,15 +1620,13 @@ begin
|
|
|
end;
|
|
|
|
|
|
function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
|
|
|
-var
|
|
|
- res: Integer;
|
|
|
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;
|
|
|
|
|
|
function TDOMNamedNodeMap.Delete(index: LongWord): TDOMNode;
|
|
@@ -1670,12 +1680,112 @@ end;
|
|
|
|
|
|
function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
|
|
|
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;
|
|
|
+ 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;
|
|
|
|
|
|
+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
|
|
@@ -2353,7 +2463,10 @@ end;
|
|
|
function TDOMAttr.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
|
|
|
begin
|
|
|
// 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;
|
|
|
CloneChildren(Result, ACloneOwner);
|
|
|
end;
|
|
@@ -2390,7 +2503,6 @@ begin
|
|
|
Include(FFlags, nfDestroying);
|
|
|
if Assigned(FOwnerDocument.FIDList) then
|
|
|
FOwnerDocument.RemoveID(Self);
|
|
|
- // FIX: Attribute nodes are now freed by TDOMNamedNodeMap.Destroy
|
|
|
FreeAndNil(FAttributes);
|
|
|
inherited Destroy;
|
|
|
end;
|
|
@@ -2398,12 +2510,45 @@ end;
|
|
|
function TDOMElement.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
|
|
|
var
|
|
|
i: Integer;
|
|
|
+ Attr, AttrClone: TDOMAttr;
|
|
|
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
|
|
|
- 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;
|
|
|
if deep then
|
|
|
CloneChildren(Result, ACloneOwner);
|
|
@@ -2450,7 +2595,7 @@ end;
|
|
|
function TDOMElement.GetAttributes: TDOMNamedNodeMap;
|
|
|
begin
|
|
|
if FAttributes=nil then
|
|
|
- FAttributes := TDOMNamedNodeMap.Create(Self, ATTRIBUTE_NODE);
|
|
|
+ FAttributes := TAttributeMap.Create(Self, ATTRIBUTE_NODE);
|
|
|
Result := FAttributes;
|
|
|
end;
|
|
|
|
|
@@ -2509,13 +2654,14 @@ procedure TDOMElement.RemoveAttributeNS(const nsURI,
|
|
|
aLocalName: DOMString);
|
|
|
begin
|
|
|
Changing;
|
|
|
- // TODO: Implement TDOMElement.RemoveAttributeNS
|
|
|
- raise EDOMNotSupported.Create('TDOMElement.RemoveAttributeNS');
|
|
|
+ if Assigned(FAttributes) then
|
|
|
+ TAttributeMap(FAttributes).InternalRemoveNS(nsURI, aLocalName).Free;
|
|
|
end;
|
|
|
|
|
|
procedure TDOMElement.SetAttributeNS(const nsURI, qualifiedName,
|
|
|
value: DOMString);
|
|
|
var
|
|
|
+ I: Cardinal;
|
|
|
Attr: TDOMAttr;
|
|
|
idx, prefIdx: Integer;
|
|
|
begin
|
|
@@ -2524,14 +2670,27 @@ begin
|
|
|
prefIdx := CheckQName(qualifiedName, idx, FOwnerDocument.FXml11);
|
|
|
if prefIdx < 0 then
|
|
|
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
|
|
|
- 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;
|
|
|
end;
|
|
|
|