2004-04-05 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlDocumentNavigator.cs
index 4c763000834d3b8b33b9f91b64fc1b935a855603..c00339be618c864f4fdb63caa188016738b53112 100644 (file)
@@ -1,52 +1,75 @@
 //
 // System.Xml.XmlDocumentNavigator
 //
-// Author:
+// Authors:
 //   Jason Diamond <jason@injektilo.org>
+//   Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
 //
 // (C) 2002 Jason Diamond
+// (C) 2003 Atsushi Enomoto
 //
-\r
-using System;\r
-using System.Collections;\r
-using System.Xml;\r
-using System.Xml.XPath;\r
-\r
-namespace System.Xml\r
-{\r
-       internal class XmlDocumentNavigator : XPathNavigator\r
-       {\r
-               #region Constructors\r
-\r
-               [MonoTODO]
-               internal XmlDocumentNavigator(XmlNode node)\r
-               {\r
-                       this.node = node;\r
-               }\r
-\r
-               #endregion\r
-\r
-               #region Fields\r
-\r
-               private XmlNode node;\r
-               private IEnumerator attributesEnumerator;\r
-\r
-               #endregion\r
+
+using System;
+using System.Collections;
+using System.Xml;
+using System.Xml.XPath;
+
+namespace System.Xml
+{
+       internal class XmlDocumentNavigator : XPathNavigator, IHasXmlNode
+       {
+               #region Constructors
+
+               internal XmlDocumentNavigator (XmlNode node)
+                       : this (node, null)
+               {
+                       nsNodeXml = document.CreateAttribute ("xmlns", "xml", Xmlns);
+                       nsNodeXml.Value = XmlnsXML;
+                       if (node.NodeType == XmlNodeType.Attribute && node.NamespaceURI == XmlNamespaceManager.XmlnsXmlns) {
+                               nsNode = (XmlAttribute) node; 
+                               node = nsNode.OwnerElement;
+                       }
+               }
+
+               private XmlDocumentNavigator (XmlNode node, XmlAttribute nsNodeXml)
+               {
+                       this.node = node;
+                       this.document = node.NodeType == XmlNodeType.Document ?
+                               node as XmlDocument : node.OwnerDocument;
+                       this.nsNodeXml = nsNodeXml;
+               }
+
+               #endregion
+
+               #region Fields
+               private const string Xmlns = "http://www.w3.org/2000/xmlns/";
+               private const string XmlnsXML = "http://www.w3.org/XML/1998/namespace";
+
+               private XmlAttribute nsNodeXml;
+               private XmlNode node;
+               private XmlDocument document;
+               // Current namespace node (ancestor's attribute of current node).
+               private XmlAttribute nsNode;
+               private ArrayList iteratedNsNames = new ArrayList ();
+
+               #endregion
 
                #region Properties
 
-               [MonoTODO]
                public override string BaseURI {
                        get {
-                               throw new NotImplementedException ();
+                               return node.BaseURI;
                        }
                }
 
                public override bool HasAttributes {
                        get {
+                               if (NsNode != null)
+                                       return false;
+
                                if (node.Attributes != null)
-                                       foreach (XmlAttribute attribute in node.Attributes)
-                                               if (attribute.NamespaceURI != "http://www.w3.org/2000/xmlns/")
+                                       for (int i = 0; i < node.Attributes.Count; i++)
+                                               if (node.Attributes [i].NamespaceURI != Xmlns)
                                                        return true;
                                return false;
                        }
@@ -54,6 +77,9 @@ namespace System.Xml
 
                public override bool HasChildren {
                        get {
+                               if (NsNode != null)
+                                       return false;
+
                                XPathNodeType nodeType = NodeType;
                                bool canHaveChildren = nodeType == XPathNodeType.Root || nodeType == XPathNodeType.Element;
                                return canHaveChildren && node.FirstChild != null;
@@ -62,12 +88,35 @@ namespace System.Xml
 
                public override bool IsEmptyElement {
                        get {
-                               return node.NodeType == XmlNodeType.Element && !HasChildren;
+                               if (NsNode != null)
+                                       return false;
+
+                               return node.NodeType == XmlNodeType.Element 
+                                       && ((XmlElement) node).IsEmpty;
+                       }
+               }
+
+               public XmlAttribute NsNode {
+                       get { return nsNode; }
+                       set {
+                               if (value == null)
+                                       iteratedNsNames.Clear ();
+                               else
+                                       iteratedNsNames.Add (value.Name);
+                               nsNode = value;
                        }
                }
 
                public override string LocalName {
                        get {
+                               XmlAttribute nsNode = NsNode;
+                               if (nsNode != null) {
+                                       if (nsNode == nsNodeXml)
+                                               return "xml";
+                                       else
+                                               return (nsNode.Name == "xmlns") ? String.Empty : nsNode.LocalName;
+                               }
+
                                XPathNodeType nodeType = NodeType;
                                bool canHaveName = 
                                        nodeType == XPathNodeType.Element || 
@@ -80,6 +129,9 @@ namespace System.Xml
 
                public override string Name {
                        get {
+                               if (NsNode != null)
+                                       return LocalName;
+
                                XPathNodeType nodeType = NodeType;
                                bool canHaveName = 
                                        nodeType == XPathNodeType.Element || 
@@ -91,76 +143,58 @@ namespace System.Xml
                }
 
                public override string NamespaceURI {
-                       get {
-                               return node.NamespaceURI;
-                       }
+                       get { return (NsNode != null) ? String.Empty : node.NamespaceURI; }
                }
 
-               [MonoTODO]
                public override XmlNameTable NameTable {
                        get {
-                               throw new NotImplementedException ();
+                               return document.NameTable;
                        }
                }
 
                public override XPathNodeType NodeType {
-                       get {
-                               switch (node.NodeType) {
-                               case XmlNodeType.Document:
-                                       return XPathNodeType.Root;
-                               case XmlNodeType.Element:
-                                       return XPathNodeType.Element;
-                               case XmlNodeType.Attribute:
-                                       return XPathNodeType.Attribute;
-                               case XmlNodeType.Text:
-                                       return XPathNodeType.Text;
-                               case XmlNodeType.Whitespace:
-                                       return XPathNodeType.Whitespace;
-                               case XmlNodeType.SignificantWhitespace:
-                                       return XPathNodeType.SignificantWhitespace;
-                               case XmlNodeType.Comment:
-                                       return XPathNodeType.Comment;
-                               case XmlNodeType.ProcessingInstruction:
-                                       return XPathNodeType.ProcessingInstruction;
-                               }
-                               throw new InvalidOperationException ();
-                       }
+                       get { return (NsNode != null) ? XPathNodeType.Namespace : node.XPathNodeType; }
                }
 
                public override string Prefix {
-                       get {
-                               return node.Prefix;
-                       }
+                       get { return (NsNode != null) ? String.Empty : node.Prefix; }
                }
 
                public override string Value {
                        get {
                                switch (NodeType) {
                                case XPathNodeType.Attribute:
-                                       return node.Value;
-                               case XPathNodeType.Element:
-                                       return node.InnerText;
                                case XPathNodeType.Comment:
-                                       return node.Value;
                                case XPathNodeType.ProcessingInstruction:
                                        return node.Value;
                                case XPathNodeType.Text:
-                                       return node.Value;
                                case XPathNodeType.Whitespace:
-                                       return node.Value;
                                case XPathNodeType.SignificantWhitespace:
-                                       return node.Value;
+                                       string value = node.Value;
+                                       for (XmlNode n = node.NextSibling; n != null; n = n.NextSibling) {
+                                               switch (n.XPathNodeType) {
+                                               case XPathNodeType.Text:
+                                               case XPathNodeType.Whitespace:
+                                               case XPathNodeType.SignificantWhitespace:
+                                                       value += n.Value;
+                                                       continue;
+                                               }
+                                               break;
+                                       }
+                                       return value;
+                               case XPathNodeType.Element:
                                case XPathNodeType.Root:
                                        return node.InnerText;
+                               case XPathNodeType.Namespace:
+                                       return NsNode == nsNodeXml ? XmlnsXML : NsNode.Value;
                                }
                                return String.Empty;
                        }
                }
 
-               [MonoTODO]
                public override string XmlLang {
                        get {
-                               throw new NotImplementedException ();
+                               return node.XmlLang;
                        }
                }
 
@@ -168,28 +202,51 @@ namespace System.Xml
 
                #region Methods
 
+               private bool CheckNsNameAppearance (string name, string ns)
+               {
+                       if (iteratedNsNames.Contains (name))
+                               return true;
+                       // default namespace erasure - just add name and never return this node
+                       if (ns == String.Empty) {
+                               iteratedNsNames.Add ("xmlns");
+                               return true;
+                       }
+
+                       return false;
+               }
+
                public override XPathNavigator Clone ()
                {
-                       return new XmlDocumentNavigator (node);
+                       XmlDocumentNavigator clone = new XmlDocumentNavigator (node, nsNodeXml);
+                       clone.nsNode = nsNode;
+                       clone.iteratedNsNames = (ArrayList) iteratedNsNames.Clone ();
+                       return clone;
                }
 
-               [MonoTODO]
                public override string GetAttribute (string localName, string namespaceURI)
                {
-                       throw new NotImplementedException ();
+                       if (HasAttributes) {
+                               XmlElement el = Node as XmlElement;
+                               return el != null ? el.GetAttribute (localName, namespaceURI) : String.Empty;
+                       }
+                       return String.Empty;
                }
 
-               [MonoTODO]
                public override string GetNamespace (string name)
                {
-                       throw new NotImplementedException ();
+                       // MSDN says "String.Empty if a matching namespace 
+                       // node is not found or if the navigator is not 
+                       // positioned on an element node", but in fact it
+                       // returns actual namespace for the other nodes.
+                       return Node.GetNamespaceOfPrefix (name);
                }
                
                public override bool IsSamePosition (XPathNavigator other)
                {
                        XmlDocumentNavigator otherDocumentNavigator = other as XmlDocumentNavigator;
                        if (otherDocumentNavigator != null)
-                               return node == otherDocumentNavigator.node;
+                               return node == otherDocumentNavigator.node
+                                       && NsNode == otherDocumentNavigator.NsNode;
                        return false;
                }
 
@@ -197,24 +254,39 @@ namespace System.Xml
                {
                        XmlDocumentNavigator otherDocumentNavigator = other as XmlDocumentNavigator;
                        if (otherDocumentNavigator != null) {
-                               if (node.OwnerDocument == otherDocumentNavigator.node.OwnerDocument) {
+                               if (document == otherDocumentNavigator.document) {
                                        node = otherDocumentNavigator.node;
+                                       NsNode = otherDocumentNavigator.NsNode;
                                        return true;
                                }
                        }
                        return false;
                }
 
-               [MonoTODO]
                public override bool MoveToAttribute (string localName, string namespaceURI)
                {
-                       throw new NotImplementedException ();
+                       if (node.Attributes != null) {
+                               for (int i = 0; i < node.Attributes.Count; i++) {
+                                       XmlAttribute attr = node.Attributes [i];
+                                       if (attr.LocalName == localName
+                                               && attr.NamespaceURI == namespaceURI) {
+                                               node = attr;
+                                               NsNode = null;
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
                }
 
                public override bool MoveToFirst ()
                {
-                       if (node.NodeType != XmlNodeType.Attribute && node.ParentNode != null) {
-                               node = node.ParentNode.FirstChild;
+                       if (NsNode == null && node.NodeType != XmlNodeType.Attribute && node.ParentNode != null) {
+                               if (!MoveToParent ())
+                                       return false;
+                               // Follow these 2 steps so that we can skip 
+                               // some types of nodes .
+                               MoveToFirstChild ();
                                return true;
                        }
                        return false;
@@ -222,9 +294,17 @@ namespace System.Xml
 
                public override bool MoveToFirstAttribute ()
                {
+                       if (node.Attributes == null)
+                               return false;
                        if (NodeType == XPathNodeType.Element) {
-                               attributesEnumerator = node.Attributes.GetEnumerator ();
-                               return MoveToNextAttribute ();
+                               for (int i = 0; i < node.Attributes.Count; i++) {
+                                       XmlAttribute attr = node.Attributes [i];
+                                       if (attr.NamespaceURI != Xmlns) {
+                                               node = attr;
+                                               NsNode = null;
+                                               return true;
+                                       }
+                               }
                        }
                        return false;
                }
@@ -232,64 +312,253 @@ namespace System.Xml
                public override bool MoveToFirstChild ()
                {
                        if (HasChildren) {
-                               node = node.FirstChild;
+                               if (node == document) {
+                                       XmlNode n = node.FirstChild;
+                                       if (n == null)
+                                               return false;
+                                       bool loop = true;
+                                       do {
+                                               switch (n.NodeType) {
+                                               case XmlNodeType.XmlDeclaration:
+                                               case XmlNodeType.DocumentType:
+                                                       n = n.NextSibling;
+                                                       if (n == null)
+                                                               return false;
+                                                       break;
+                                               default:
+                                                       loop = false;
+                                                       break;
+                                               }
+                                       } while (loop);
+                                       node = n;
+                               } else {
+                                       do {
+                                               node = node.FirstChild;
+                                               if (node.NodeType != XmlNodeType.EntityReference)
+                                                       break;
+                                               node = node.NextSibling;
+                                       } while (node != null);
+                                       if (node == null)
+                                               return false;
+                               }
                                return true;
                        }
                        return false;
                }
 
-               [MonoTODO]
                public override bool MoveToFirstNamespace (XPathNamespaceScope namespaceScope)
                {
-                       throw new NotImplementedException ();
+                       if (NodeType != XPathNodeType.Element)
+                               return false;
+                       XmlElement el = node as XmlElement;
+                       if (node.Attributes != null) {
+                               do {
+                                       for (int i = 0; i < el.Attributes.Count; i++) {
+                                               XmlAttribute attr = el.Attributes [i];
+                                               if (attr.NamespaceURI == Xmlns) {
+                                                       if (CheckNsNameAppearance (attr.Name, attr.Value))
+                                                               continue;
+                                                       NsNode = attr;
+                                                       return true;
+                                               }
+                                       }
+                                       if (namespaceScope == XPathNamespaceScope.Local)
+                                               return false;
+                                       el = el.ParentNode as XmlElement;
+                               } while (el != null);
+                       }
+
+                       if (namespaceScope == XPathNamespaceScope.All) {
+                               if (CheckNsNameAppearance (nsNodeXml.Name, nsNodeXml.Value))
+                                       return false;
+                               NsNode = nsNodeXml;
+                               return true;
+                       }
+                       else
+                               return false;
                }
 
-               [MonoTODO]
                public override bool MoveToId (string id)
                {
-                       throw new NotImplementedException ();
+                       XmlElement eltNew = document.GetElementById (id);
+                       if (eltNew == null)
+                               return false;
+
+                       node = eltNew;
+                       return true;
                }
 
-               [MonoTODO]
                public override bool MoveToNamespace (string name)
                {
-                       throw new NotImplementedException ();
+                       if (name == "xml") {
+                               NsNode = nsNodeXml;
+                               return true;
+                       }
+
+                       if (NodeType != XPathNodeType.Element)
+                               return false;
+
+                       XmlElement el = node as XmlElement;
+                       if (node.Attributes != null) {
+                               do {
+                                       for (int i = 0; i < el.Attributes.Count; i++) {
+                                               XmlAttribute attr = el.Attributes [i];
+                                               if (attr.NamespaceURI == Xmlns && attr.Name == name) {
+                                                       NsNode = attr;
+                                                       return true;
+                                               }
+                                       }
+                                       el = node.ParentNode as XmlElement;
+                               } while (el != null);
+                       }
+                       return false;
                }
 
                public override bool MoveToNext ()
                {
+                       if (NsNode != null)
+                               return false;
+
                        if (node.NextSibling != null) {
-                               node = node.NextSibling;
+                               XmlNode n = node.NextSibling;
+                               if (node.ParentNode != null && node.ParentNode.NodeType == XmlNodeType.Document) {
+                                       while (n != null) {
+                                               switch (n.NodeType) {
+                                               case XmlNodeType.DocumentType:
+                                               case XmlNodeType.XmlDeclaration:
+                                                       n = n.NextSibling;
+                                                       continue;
+                                               }
+                                               break;
+                                       }
+                                       if (n != null)
+                                               node = n;
+                                       else
+                                               return false;
+                               } else {
+                                       while (n != null) {
+                                               if (n.NodeType != XmlNodeType.EntityReference)
+                                                       break;
+                                               n = n.NextSibling;
+                                       }
+                                       if (n != null)
+                                               node = n;
+                                       else
+                                               return false;
+                               }
                                return true;
                        }
-                       return false;
+                       else
+                               return false;
                }
 
                public override bool MoveToNextAttribute ()
                {
-                       if (attributesEnumerator != null && attributesEnumerator.MoveNext ()) {
-                               node = attributesEnumerator.Current as XmlAttribute;
-                               return true;
+                       if (node == null)
+                               return false;
+                       if (NodeType != XPathNodeType.Attribute)
+                               return false;
+
+                       // Find current attribute.
+                       int pos = 0;
+                       XmlElement owner = ((XmlAttribute) node).OwnerElement;
+                       if (owner == null)
+                               return false;
+
+                       int count = owner.Attributes.Count;
+                       for(; pos < count; pos++)
+                               if (owner.Attributes [pos] == node)
+                                       break;
+                       if (pos == count)
+                               return false;   // Where is current attribute? Maybe removed.
+
+                       // Find next attribute.
+                       for(pos++; pos < count; pos++) {
+                               if (owner.Attributes [pos].NamespaceURI != Xmlns) {
+                                       node = owner.Attributes [pos];
+                                       NsNode = null;
+                                       return true;
+                               }
                        }
                        return false;
                }
 
-               [MonoTODO]
                public override bool MoveToNextNamespace (XPathNamespaceScope namespaceScope)
                {
-                       throw new NotImplementedException ();
+                       if (NsNode == nsNodeXml)
+                               // Current namespace is "xml", so there should be no more namespace nodes.
+                               return false;
+
+                       if (NsNode == null)
+                               return false;
+
+                       // Get current attribute's position.
+                       int pos = 0;
+                       XmlElement owner = ((XmlAttribute) NsNode).OwnerElement;
+                       if (owner == null)
+                               return false;
+
+                       int count = owner.Attributes.Count;
+                       for(; pos < count; pos++)
+                               if (owner.Attributes [pos] == NsNode)
+                                       break;
+                       if (pos == count)
+                               return false;   // Where is current attribute? Maybe removed.
+
+                       // Find next namespace from the same element as current ns node.
+                       for(pos++; pos < count; pos++) {
+                               if (owner.Attributes [pos].NamespaceURI == Xmlns) {
+                                       XmlAttribute a = owner.Attributes [pos];
+                                       if (CheckNsNameAppearance (a.Name, a.Value))
+                                               continue;
+                                       NsNode = a;
+                                       return true;
+                               }
+                       }
+
+                       // If not found more, then find from ancestors.
+                       // But if scope is Local, then it returns false here.
+                       if (namespaceScope == XPathNamespaceScope.Local)
+                               return false;
+                       owner = owner.ParentNode as XmlElement;
+                       while (owner != null) {
+                               for (int i = 0; i < owner.Attributes.Count; i++) {
+                                       XmlAttribute attr = owner.Attributes [i];
+                                       if (attr.NamespaceURI == Xmlns) {
+                                               if (CheckNsNameAppearance (attr.Name, attr.Value))
+                                                       continue;
+                                               NsNode = attr;
+                                               return true;
+                                       }
+                               }
+                               owner = owner.ParentNode as XmlElement;
+                       }
+
+                       if (namespaceScope == XPathNamespaceScope.All) {
+                               if (CheckNsNameAppearance (nsNodeXml.Name, nsNodeXml.Value))
+                                       return false;
+                               NsNode = nsNodeXml;
+                               return true;
+                       }
+                       return false;
                }
 
                public override bool MoveToParent ()
                {
-                       if (node.NodeType == XmlNodeType.Attribute) {
+                       if (NsNode != null) {
+                               NsNode = null;
+                               return true;
+                       }
+                       else if (node.NodeType == XmlNodeType.Attribute) {
                                XmlElement ownerElement = ((XmlAttribute)node).OwnerElement;
                                if (ownerElement != null) {
                                        node = ownerElement;
+                                       NsNode = null;
                                        return true;
                                }
                        } else if (node.ParentNode != null) {
                                node = node.ParentNode;
+                               NsNode = null;
                                return true;
                        }
                        return false;
@@ -297,21 +566,52 @@ namespace System.Xml
 
                public override bool MoveToPrevious ()
                {
+                       if (NsNode != null)
+                               return false;
+
                        if (node.PreviousSibling != null) {
-                               node = node.PreviousSibling;
+                               if (node.ParentNode != null && node.ParentNode.NodeType == XmlNodeType.Document) {
+                                       XmlNode n = node.PreviousSibling;
+                                       while (n != null) {
+                                               switch (n.NodeType) {
+                                               case XmlNodeType.DocumentType:
+                                               case XmlNodeType.XmlDeclaration:
+                                                       n = n.PreviousSibling;
+                                                       continue;
+                                               }
+                                               break;
+                                       }
+                                       if (n != null)
+                                               node = n;
+                                       else
+                                               return false;
+                               }
+                               else
+                                       node = node.PreviousSibling;
+                               
                                return true;
                        }
-                       return false;
+                       else
+                               return false;
                }
 
                public override void MoveToRoot ()
                {
-                       if (node.NodeType != XmlNodeType.Document)
-                               node = node.OwnerDocument;
+                       XmlAttribute attr = node as XmlAttribute;
+                       XmlNode tmp = attr != null ? attr.OwnerElement : node;
+                       while (tmp.ParentNode != null)
+                               tmp = tmp.ParentNode;
+                       node = tmp;
+                       NsNode = null;
                }
 
-               internal XmlNode Node { get { return node; } }
+               internal XmlNode Node { get { return NsNode != null ? NsNode : node; } }
+
+                XmlNode IHasXmlNode.GetNode ()
+                {
+                        return node;
+                }
 
                #endregion
-       }\r
-}\r
+       }
+}