2004-02-06 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlNode.cs
index 9e68f17ed116aee771433b54fa343612bd00f28f..7e779dc28e4168b09a3366d7ca44405ab51314e0 100644 (file)
@@ -24,6 +24,7 @@ namespace System.Xml
                XmlDocument ownerDocument;
                XmlNode parentNode;
                StringBuilder tmpBuilder;
+               XmlLinkedNode lastLinkedChild;
 
                #endregion
 
@@ -152,8 +153,8 @@ namespace System.Xml
                }
 
                internal virtual XmlLinkedNode LastLinkedChild {
-                       get { return null; }
-                       set { }
+                       get { return lastLinkedChild; }
+                       set { lastLinkedChild = value; }
                }
 
                public abstract string LocalName { get; }
@@ -318,7 +319,7 @@ namespace System.Xml
                                break;
                        }
 
-                       while (node.NodeType != XmlNodeType.Document) {
+                       while (node != null && node.Attributes != null) {
                                foreach (XmlAttribute attr in node.Attributes) {
                                        if (attr.Prefix == "xmlns" && attr.Value == namespaceURI)
                                                return attr.LocalName;
@@ -342,22 +343,110 @@ namespace System.Xml
 
                public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
                {
-                       // I assume that insertAfter(n1, n2) equals to InsertBefore(n1, n2.PreviousSibling).
+                       // InsertAfter(n1, n2) is equivalent to InsertBefore(n1, n2.PreviousSibling).
 
                        // I took this way because current implementation 
-                       // Calling InsertAfter() from InsertBefore() is
-                       // subsequently to use 'NextSibling' which is
-                       // faster than 'PreviousSibling' (these children are 
-                       // forward-only linked list).
+                       // Calling InsertBefore() in this method is faster than
+                       // the counterpart, since NextSibling is faster than 
+                       // PreviousSibling (these children are forward-only list).
                        XmlNode argNode = null;
-                       if(refChild != null)
+                       if (refChild != null)
                                argNode = refChild.NextSibling;
-                       else if(ChildNodes.Count > 0)
+                       else if (ChildNodes.Count > 0)
                                argNode = FirstChild;
                        return InsertBefore (newChild, argNode);
                }
 
                public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
+               {
+                       return InsertBefore (newChild, refChild, true, true);
+               }
+
+               // check for the node to be one of node ancestors
+               internal bool IsAncestor (XmlNode newChild)
+               {
+                       XmlNode currNode = this.ParentNode;
+                       while(currNode != null)
+                       {
+                               if(currNode == newChild)
+                                       return true;
+                               currNode = currNode.ParentNode;
+                       }
+                       return false;
+               }
+
+               internal XmlNode InsertBefore (XmlNode newChild, XmlNode refChild, bool checkNodeType, bool raiseEvent)
+               {
+                       if (checkNodeType)
+                               CheckNodeInsertion (newChild, refChild);
+
+                       XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
+
+                       if (raiseEvent)
+                               ownerDoc.onNodeInserting (newChild, this);
+
+                       if (newChild.ParentNode != null)
+                               newChild.ParentNode.RemoveChild (newChild, checkNodeType);
+
+                       if (newChild.NodeType == XmlNodeType.DocumentFragment) {
+                               int x = newChild.ChildNodes.Count;
+                               for (int i = 0; i < x; i++) {
+                                       XmlNode n = newChild.ChildNodes [0];
+                                       this.InsertBefore (n, refChild);        // recursively invokes events. (It is compatible with MS implementation.)
+                               }
+                       }
+                       else {
+                               XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
+                               XmlLinkedNode lastLinkedChild = LastLinkedChild;
+
+                               newLinkedChild.parentNode = this;
+
+                               if (refChild == null) {
+                                       // newChild is the last child:
+                                       // * set newChild as NextSibling of the existing lastchild
+                                       // * set LastChild = newChild
+                                       // * set NextSibling of newChild as FirstChild
+                                       if (LastLinkedChild != null) {
+                                               XmlLinkedNode formerFirst = (XmlLinkedNode) FirstChild;
+                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;
+                                               LastLinkedChild = newLinkedChild;
+                                               newLinkedChild.NextLinkedSibling = formerFirst;
+                                       } else {
+                                               LastLinkedChild = newLinkedChild;
+                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;     // FirstChild
+                                       }
+                               } else {
+                                       // newChild is not the last child:
+                                       // * if newchild is first, then set next of lastchild is newChild.
+                                       //   otherwise, set next of previous sibling to newChild
+                                       // * set next of newChild to refChild
+                                       XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
+                                       if (prev == null)
+                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;
+                                       else
+                                               prev.NextLinkedSibling = newLinkedChild;
+                                       newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
+                               }
+                               switch (newChild.NodeType) {
+                               case XmlNodeType.EntityReference:
+                                       ((XmlEntityReference) newChild).SetReferencedEntityContent ();
+                                       break;
+                               case XmlNodeType.Entity:
+                                       ((XmlEntity) newChild).SetEntityContent ();
+                                       break;
+                               case XmlNodeType.DocumentType:
+                                       foreach (XmlEntity ent in ((XmlDocumentType)newChild).Entities)
+                                               ent.SetEntityContent ();
+                                       break;
+                               }
+
+                               if (raiseEvent)
+                                       ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
+                       }
+                       return newChild;
+               }
+
+               private void CheckNodeInsertion (XmlNode newChild, XmlNode refChild)
                {
                        XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
 
@@ -401,69 +490,15 @@ namespace System.Xml
                        if (refChild != null && newChild.OwnerDocument != refChild.OwnerDocument)
                                        throw new ArgumentException ("argument nodes are on the different documents.");
 
-                       // This check is done by MS.NET 1.0, but isn't done for MS.NET 1.1. 
-                       // Skip this check in the meantime...
-//                             if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement))
-//                                     throw new XmlException ("multiple document element not allowed.");
+                       if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement))
+                               throw new XmlException ("multiple document element not allowed.");
 
                        // checking validity finished. then appending...
 
-                       return insertBeforeIntern (newChild, refChild);
-               }
-
-               internal XmlNode insertBeforeIntern (XmlNode newChild, XmlNode refChild)
-               {
-                       XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
+                       
+                       if (newChild == this || IsAncestor (newChild))
+                               throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
 
-                       ownerDoc.onNodeInserting (newChild, this);
-
-                       if(newChild.ParentNode != null)
-                               newChild.ParentNode.RemoveChild (newChild);
-
-                       if(newChild.NodeType == XmlNodeType.DocumentFragment) {
-                               int x = newChild.ChildNodes.Count;
-                               for(int i=0; i<x; i++) {
-                                       XmlNode n = newChild.ChildNodes [0];
-                                       this.InsertBefore (n, refChild);        // recursively invokes events. (It is compatible with MS implementation.)
-                               }
-                       }
-                       else {
-                               XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
-                               XmlLinkedNode lastLinkedChild = LastLinkedChild;
-
-                               newLinkedChild.parentNode = this;
-
-                               if(refChild == null) {
-                                       // append last, so:
-                                       // * set nextSibling of previous lastchild to newChild
-                                       // * set lastchild = newChild
-                                       // * set next of newChild to firstChild
-                                       if(LastLinkedChild != null) {
-                                               XmlLinkedNode formerFirst = FirstChild as XmlLinkedNode;
-                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;
-                                               LastLinkedChild = newLinkedChild;
-                                               newLinkedChild.NextLinkedSibling = formerFirst;
-                                       }
-                                       else {
-                                               LastLinkedChild = newLinkedChild;
-                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;     // FirstChild
-                                       }
-                               }
-                               else {
-                                       // append not last, so:
-                                       // * if newchild is first, then set next of lastchild is newChild.
-                                       //   otherwise, set next of previous sibling to newChild
-                                       // * set next of newChild to refChild
-                                       XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
-                                       if(prev == null)
-                                               LastLinkedChild.NextLinkedSibling = newLinkedChild;
-                                       else
-                                               prev.NextLinkedSibling = newLinkedChild;
-                                       newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
-                               }
-                               ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
-                       }
-                       return newChild;
                }
 
                public virtual void Normalize ()
@@ -544,12 +579,11 @@ namespace System.Xml
 
                public virtual XmlNode RemoveChild (XmlNode oldChild)
                {
-                       XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
-                       if(oldChild.ParentNode != this)
-                               throw new XmlException ("specified child is not child of this node.");
-
-                       ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
+                       return RemoveChild (oldChild, true);
+               }
 
+               private void CheckNodeRemoval ()
+               {
                        if (NodeType != XmlNodeType.Attribute && 
                                NodeType != XmlNodeType.Element && 
                                NodeType != XmlNodeType.Document && 
@@ -558,6 +592,19 @@ namespace System.Xml
 
                        if (IsReadOnly)
                                throw new ArgumentException (String.Format ("This {0} node is read only.", NodeType));
+               }
+
+               internal XmlNode RemoveChild (XmlNode oldChild, bool checkNodeType)
+               {
+                       XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
+                       if(oldChild.ParentNode != this)
+                               throw new XmlException ("specified child is not child of this node.");
+
+                       if (checkNodeType)
+                               ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
+
+                       if (checkNodeType)
+                               CheckNodeRemoval ();
 
                        if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
                                // If there is only one children, simply clear.
@@ -583,7 +630,8 @@ namespace System.Xml
                                oldLinkedChild.NextLinkedSibling = null;
                                }
 
-                       ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
+                       if (checkNodeType)
+                               ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
                        oldChild.parentNode = null;     // clear parent 'after' above logic.
 
                        return oldChild;
@@ -593,12 +641,10 @@ namespace System.Xml
                {
                        if(oldChild.ParentNode != this)
                                throw new InvalidOperationException ("oldChild is not a child of this node.");
-                       XmlNode parent = this.ParentNode;
-                       while(parent != null) {
-                               if(newChild == parent)
-                                       throw new InvalidOperationException ("newChild is ancestor of this node.");
-                               parent = parent.ParentNode;
-                       }
+                       
+                       if (newChild == this || IsAncestor (newChild))
+                               throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
+                       
                        foreach(XmlNode n in ChildNodes) {
                                if(n == oldChild) {
                                        XmlNode prev = oldChild.PreviousSibling;
@@ -610,12 +656,34 @@ namespace System.Xml
                        return oldChild;
                }
 
+               internal void SearchDescendantElements (string name, bool matchAll, ArrayList list)
+               {
+                       foreach (XmlNode n in ChildNodes){
+                               if (n.NodeType != XmlNodeType.Element)
+                                       continue;
+                               if (matchAll || n.Name == name)
+                                       list.Add (n);
+                               n.SearchDescendantElements (name, matchAll, list);
+                       }
+               }
+
+               internal void SearchDescendantElements (string name, bool matchAllName, string ns, bool matchAllNS, ArrayList list)
+               {
+                       foreach (XmlNode n in ChildNodes){
+                               if (n.NodeType != XmlNodeType.Element)
+                                       continue;
+                               if ((matchAllName || n.LocalName == name)
+                                       && (matchAllNS || n.NamespaceURI == ns))
+                                       list.Add (n);
+                               n.SearchDescendantElements (name, matchAllName, ns, matchAllNS, list);
+                       }
+               }
+
                public XmlNodeList SelectNodes (string xpath)
                {
                        return SelectNodes (xpath, null);
                }
 
-               [MonoTODO ("return nodes in document order")]
                public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
                {
                        XPathNavigator nav = CreateNavigator ();
@@ -636,7 +704,6 @@ namespace System.Xml
                        return SelectSingleNode (xpath, null);
                }
 
-               [MonoTODO ("return node in document order")]
                public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
                {
                        XPathNavigator nav = CreateNavigator ();
@@ -665,7 +732,6 @@ namespace System.Xml
 
                // It parses this and all the ancestor elements,
                // find 'xmlns' declarations, stores and then return them.
-               // TODO: tests
                internal XmlNamespaceManager ConstructNamespaceManager ()
                {
                        XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;