// System.Xml.XmlAttributeCollection.cs
//
// Author: Daniel Weber (daniel-weber@austin.rr.com)
//
// Implementation of abstract Xml.XmlAttributeCollection class
//
using System;
using System.Collections;
namespace System.Xml
{
///
/// A collection of Attributes that can be accessed by index or name(space)
/// Derived from XmlNamedNodeMap
///
///
public class XmlAttributeCollection : XmlNamedNodeMap, ICollection
{
// ===== ICollection interface elements ===================================
///
/// Private class to provide Synchronzed Access to the attribute list.
///
private class SyncAttributes : XmlAttributeCollection
{
private XmlAttributeCollection _attributes;
public SyncAttributes ( XmlAttributeCollection attributes )
{
_attributes = attributes;
}
public override bool IsSynchronized
{
get {return true; }
}
// Override all properties/methods that modify/read items
// and lock them so they are thread-safe
public override void CopyTo(Array array, int index)
{
lock (_attributes )
{ _attributes.CopyTo(array, index); }
}
public override XmlAttribute this[string name]
{
get {
lock (_attributes ) { return _attributes[name]; }
}
}
public override XmlAttribute this[int i]
{
get {
lock (_attributes) { return _attributes[i]; }
}
}
public override XmlAttribute Append( XmlAttribute node )
{
lock (_attributes)
{ return _attributes.Append( node ); }
}
public override void CopyTo(XmlAttribute[] array, int index)
{
lock (_attributes)
{ _attributes.CopyTo(array, index); }
}
public override XmlAttribute InsertAfter(
XmlAttribute newNode,
XmlAttribute refNode)
{
lock (_attributes)
{ return _attributes.InsertAfter( newNode, refNode ); }
}
public override XmlAttribute Prepend(XmlAttribute node)
{
lock (_attributes)
{ return _attributes.Prepend(node); }
}
public override XmlAttribute Remove(XmlAttribute node)
{
lock (_attributes)
{ return _attributes.Remove( node ); }
}
public override void RemoveAll()
{
lock (_attributes)
{ _attributes.RemoveAll(); }
}
public override XmlAttribute RemoveAt(int i)
{
lock (_attributes)
{ return _attributes.RemoveAt(i); }
}
public override XmlNode SetNamedItem(XmlNode node)
{
lock (_attributes)
{ return _attributes.SetNamedItem(node); }
}
// Even ToString, since someone could come along and blow away an
// attribute while we're iterating...
public override string ToString()
{
lock (_attributes)
{ return _attributes.ToString(); }
}
} // SynchAttributes
///
/// Return true if access is synchronized (thread-safe)
///
public virtual bool IsSynchronized
{
// This version of the class is not synchronized
get
{
return false;
}
}
///
/// Return object used for synchronous access to class
///
public object SyncRoot
{
get
{
return this;
}
}
///
/// Returns a thread-safe version of the attribute collection.
///
/// Attribute collection to make thread-safe.
/// Thread-safe XmlAttributeCollection.
public static XmlAttributeCollection Synchronized(XmlAttributeCollection attributes)
{
if (attributes == null)
{
throw new ArgumentNullException("Null XmlAttributeCollection passed to Synchronized()");
}
return new SyncAttributes(attributes);
}
///
/// Copy the XmlAttributeCollection into the passed array. Index is zero-based.
///
/// Array to copy into
/// Index to start copying from
public virtual void CopyTo(Array array, int index)
{
// Let the Array handle all the errors, there's no risk to us
// TODO - should we set OwnerElement to null in clone() in CopyTo(Array, int)? (yes, using setOwnerElement())
int arrayIndex = 0;
for (int i = index; i < FnodeList.Count; i++)
{
XmlAttribute e = FnodeList[i] as XmlAttribute;
XmlAttribute theClone = e.Clone() as XmlAttribute;
theClone.setOwnerElement(null);
array.SetValue(theClone, arrayIndex);
arrayIndex++;
}
}
// XmlAttributeCollection Properties =================================
///
/// Get the attribute with the specified name
///
[System.Runtime.CompilerServices.IndexerName("ItemOf")]
public virtual XmlAttribute this[string name]
{
get
{
return GetNamedItem(name) as XmlAttribute;
}
}
///
/// Get the attribute at the specified index. The Collection is zero-based.
///
[System.Runtime.CompilerServices.IndexerName("ItemOf")]
public virtual XmlAttribute this[int i]
{
get
{
return base.Item(i) as XmlAttribute;
}
}
///
/// Get the attribute with the specifed (localName, URI)
///
[System.Runtime.CompilerServices.IndexerName("ItemOf")]
public virtual XmlAttribute this[string localName, string namespaceURI]
{
get
{
return GetNamedItem(localName, namespaceURI) as XmlAttribute;
}
}
// ============= Public methods =====================================
///
/// Appends the specified node to the attribute list
/// If the node is already in the list, it is moved to the end.
/// If a node is in the list with the same name, the node is removed and the new node is added.
///
/// Attribute node to append to the collection
/// Node was created from a differant document or node is null
///
public virtual XmlAttribute Append( XmlAttribute node )
{
// TODO - node validation? (no)
XmlAttribute retval = null;
System.Diagnostics.Debug.Assert(node != null, "Null node passed to Append()");
if (! FOwner.OwnerDocument.Equals(node.OwnerDocument))
throw new ArgumentException("Cannot append node from another document");
if (node.OwnerElement != null)
throw new ArgumentException("Cannot append node from another document");
foreach (XmlAttribute cur in FnodeList)
{
// If node is already in the collection, it is moved to the last position.
if (cur.Equals(node))
{
retval = cur;
FnodeList.Remove(cur);
}
//If an attribute with the same name is already present in the collection,
// the original attribute is removed from the collection and
// node is added to the end of the collection.
if (cur.Name == node.Name)
FnodeList.Remove(cur);
}
// add the new node to the end of the collection
// set attribute owner element? (yes)
node.setOwnerElement(FOwnerNode as XmlElement);
FnodeList.Add(node);
// return the removed item
return retval;
}
///
/// Copies all attributes in collection into the array, starting at index.
/// attribute index is zero-based.
///
/// Thrown if insufficient room to copy all elements
/// Array to copy XlmAttributes into
/// index to start copy
public virtual void CopyTo(XmlAttribute[] array, int index)
{
// Let the array handle all the errors, there's no risk to us
// TODO - should we set OwnerElement to null in clone() in CopyTo(XmlAttribute[], int)? (yes, using setOwnerElement())
int arrayIndex = 0;
for (int i = index; i < FnodeList.Count; i++)
{
XmlAttribute e = FnodeList[i] as XmlAttribute;
XmlAttribute theClone = e.Clone() as XmlAttribute;
theClone.setOwnerElement(null);
array[arrayIndex] = theClone;
arrayIndex++;
}
}
///
/// Helper function since InsertBefore/After use exact same algorithm
///
///
///
/// offset to add to Insert (0 or 1)
/// Deleted attribute
private XmlAttribute InsertHelper(XmlAttribute newNode, XmlAttribute refNode, int offset)
{
// TODO - validation? (no)
if (refNode == null)
throw new ArgumentNullException("Null refNode passed to InsertAfter()");
if (newNode == null)
throw new ArgumentNullException("Null newNode passed to InsertAfter()");
if (! newNode.OwnerDocument.Equals(refNode.OwnerDocument) )
throw new ArgumentException("Node to insert does not have same owner document as reference");
// Logically, it makes no sense to insert node "A" after node "A",
// since only one node "A" can be in the list - flag it as an error
if (newNode.Name == refNode.Name)
throw new ArgumentException("Node to insert has same name as reference node");
// Other bizarre case is if refNode.Equals(newNode)
// We'll flag this error after we check that refNode is in the list
int refNodeIndex = -1;
// Note that if newNode is in the list, then we'll get a name match
int SameNameIndex = -1;
for (int i = 0; i < FnodeList.Count; i++)
{
XmlAttribute curListNode = Item(i) as XmlAttribute;
if (curListNode.Name == refNode.Name)
refNodeIndex = i;
if (curListNode.Name == newNode.Name)
SameNameIndex = i;
}
if ( refNodeIndex == -1 )
throw new ArgumentException("Attribute [" + refNode.Name + "] is not in Collection for InsertAfter()");
// Check the obvious, InsertAfter( attr1, attr1);
if (refNode.Equals( newNode ) )
return newNode;
XmlAttribute retval = null;
if (SameNameIndex != -1)
{
// If this is newNode in the list, we'll insert it back in the right spot
// If this is another node, just remove it
retval = FnodeList[SameNameIndex] as XmlAttribute;
FnodeList.RemoveAt(SameNameIndex);
if ( SameNameIndex < refNodeIndex )
refNodeIndex--;
}
FnodeList.Insert(refNodeIndex + offset, newNode);
// TODO - set OwnerElement? (no)
//node.setOwnerElement(FOwnerNode as XmlElement);
// TODO - determine which node to return (deleted node)
return retval;
}
///
/// Insert the specifed attribute immediately after the reference node.
/// If an attribute with the same name is already in the collection, that attribute is removed and the new attribute inserted
///
/// Raised if newNode OwnerDocument differs from nodelist owner or refNode is not a member of the collection
/// New attribute to insert
/// Reference node to insert new node after
/// Inserted node
public virtual XmlAttribute InsertAfter( XmlAttribute newNode, XmlAttribute refNode)
{
return InsertHelper(newNode, refNode, 1);
}
///
/// Inserts newNode into the collection just before refNode.
/// If a node with newNode.Name is already in the list, it is removed.
///
/// Thrown if inserted attribute created from different document, refNode not found in collection or
/// refNode.Name == newNode.Name.
/// Node to insert into list
/// Node to insert before
/// Deleted node, or null if no node was deleted.
public virtual XmlAttribute InsertBefore(
XmlAttribute newNode,
XmlAttribute refNode
)
{
return InsertHelper(newNode, refNode, 0);
}
///
/// Inserts the specified node as the first node in the collection
///
/// XmlAttribute to insert
/// If node is null, or owner document does not match collection.
/// Node that was removed, or null if no node deleted.
public virtual XmlAttribute Prepend(XmlAttribute node)
{
//TODO - node validation? (no)
// TODO - set attribute owner element? (no)
//node.setOwnerElement(FOwnerNode as XmlElement);
if (FnodeList.Count > 0)
{
return InsertBefore(node, Item(0) as XmlAttribute);
}
if (node == null)
throw new ArgumentException("Cannot prepend null node");
if (! node.OwnerDocument.Equals(FOwner.OwnerDocument) )
throw new ArgumentException("Node to prepend does not have same owner document as reference");
FnodeList.Add(node);
return node;
}
///
/// Removes the requested node from the collection.
///
/// Node to remove
/// The node removed, or null if the node was not found in the collection.
public virtual XmlAttribute Remove(XmlAttribute node)
{
for (int i = 0; i < FnodeList.Count; i++)
{
XmlAttribute e = FnodeList[i] as XmlAttribute;
if (e.Equals(node))
{
FnodeList.RemoveAt(i);
return node;
}
}
// TODO - if node is a default, should we add it back with a default value? (no)
return null;
}
///
/// Removes all attributes from the collection
///
public virtual void RemoveAll()
{
// Can this be this easy?
for (int i = FnodeList.Count - 1; i > 0; i--)
{
XmlAttribute e = FnodeList[i] as XmlAttribute;
e.setOwnerElement(null);
FnodeList.RemoveAt(i);
}
// TODO - Add default attributes back in in RemoveAll()? (no)
}
///
/// Removes the attribute at the specified index
///
/// index of attribute to remove.
/// Removed node, or null if node not in collection
public virtual XmlAttribute RemoveAt(int i)
{
if ((i < 0) | ( i >= FnodeList.Count))
return null;
// TODO - if default attribute removed in RemoveAt(), add it back? (no)
XmlAttribute e = FnodeList[i] as XmlAttribute;
FnodeList.RemoveAt(i);
e.setOwnerElement(null);
return e;
}
///
/// Adds a node to the collection using it's name. If a node with the same name exists,
/// it is removed.
///
/// XmlAttribute to add.
/// If a node replaces a named node, the replaced node is deleted and returned.
/// Otherwise, null.
public override XmlNode SetNamedItem(XmlNode node)
{
return base.SetNamedItem(node);
}
public override string ToString()
{
string retval = "System.Xml.XmlAttributeCollection ";
if (FOwnerNode != null)
retval += "OwnerElement: " + FOwnerNode.Name;
foreach (XmlAttribute o in FnodeList)
{
retval += o.Name + "=" + o.Value;
}
return retval;
}
// ============= Constructors ========================================
// TODO - change constructor to pass in IEnumerator?
internal XmlAttributeCollection(XmlNode aOwner, XmlNode aOwnerNode, ArrayList nodeList) :
base(aOwner, aOwnerNode, nodeList)
{
// Makes no sense to have namespace aware on attributes
NamespaceAware = false;
}
internal XmlAttributeCollection ()
{
}
}
}