// // System.Xml.XmlDocumentNavigator // // Authors: // Jason Diamond // Atsushi Enomoto // // (C) 2002 Jason Diamond // (C) 2003 Atsushi Enomoto // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Xml; using System.Xml.Schema; using System.Xml.XPath; namespace System.Xml { internal class XmlDocumentNavigator : XPathNavigator, IHasXmlNode { #region Constructors internal XmlDocumentNavigator (XmlNode node) { this.node = node; if (node.NodeType == XmlNodeType.Attribute && node.NamespaceURI == XmlNamespaceManager.XmlnsXmlns) { nsNode = (XmlAttribute) node; node = nsNode.OwnerElement; } } #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 XmlNode node; // Current namespace node (ancestor's attribute of current node). private XmlAttribute nsNode; private ArrayList iteratedNsNames; #endregion #region Properties internal XmlDocument Document { get { return node.NodeType == XmlNodeType.Document ? node as XmlDocument : node.OwnerDocument; } } public override string BaseURI { get { return node.BaseURI; } } public override bool HasAttributes { get { if (NsNode != null) return false; XmlElement el = node as XmlElement; if (el == null || !el.HasAttributes) return false; for (int i = 0; i < node.Attributes.Count; i++) if (node.Attributes [i].NamespaceURI != Xmlns) return true; return false; } } public override bool HasChildren { get { if (NsNode != null) return false; XPathNodeType nodeType = NodeType; bool canHaveChildren = nodeType == XPathNodeType.Root || nodeType == XPathNodeType.Element; return canHaveChildren && GetFirstChild (node) != null; } } public override bool IsEmptyElement { get { if (NsNode != null) return false; return node.NodeType == XmlNodeType.Element && ((XmlElement) node).IsEmpty; } } public XmlAttribute NsNode { get { return nsNode; } set { if (value == null) iteratedNsNames = null; else { if (iteratedNsNames == null) iteratedNsNames = new ArrayList(); else { if (iteratedNsNames.IsReadOnly) iteratedNsNames = new ArrayList(iteratedNsNames); } iteratedNsNames.Add (value.Name); } nsNode = value; } } public override string LocalName { get { XmlAttribute nsNode = NsNode; if (nsNode != null) { if (nsNode == Document.NsNodeXml) return "xml"; else return (nsNode.Name == "xmlns") ? String.Empty : nsNode.LocalName; } XPathNodeType nodeType = NodeType; bool canHaveName = nodeType == XPathNodeType.Element || nodeType == XPathNodeType.Attribute || nodeType == XPathNodeType.ProcessingInstruction || nodeType == XPathNodeType.Namespace; return canHaveName ? node.LocalName : String.Empty; } } public override string Name { get { if (NsNode != null) return LocalName; XPathNodeType nodeType = NodeType; bool canHaveName = nodeType == XPathNodeType.Element || nodeType == XPathNodeType.Attribute || nodeType == XPathNodeType.ProcessingInstruction || nodeType == XPathNodeType.Namespace; return canHaveName ? node.Name : String.Empty; } } public override string NamespaceURI { get { return (NsNode != null) ? String.Empty : node.NamespaceURI; } } public override XmlNameTable NameTable { get { return Document.NameTable; } } public override XPathNodeType NodeType { get { if (NsNode != null) return XPathNodeType.Namespace; XmlNode n = node; bool sw = false; do { switch (n.NodeType) { case XmlNodeType.SignificantWhitespace: sw = true; n = GetNextSibling (n); break; case XmlNodeType.Whitespace: n = GetNextSibling (n); break; case XmlNodeType.Text: case XmlNodeType.CDATA: return XPathNodeType.Text; default: n = null; break; } } while (n != null); return sw ? XPathNodeType.SignificantWhitespace : node.XPathNodeType; } } public override string Prefix { get { return (NsNode != null) ? String.Empty : node.Prefix; } } #if NET_2_0 public override IXmlSchemaInfo SchemaInfo { get { return NsNode != null ? null : node.SchemaInfo; } } public override object UnderlyingObject { get { return node; } } #endif public override string Value { get { switch (NodeType) { case XPathNodeType.Attribute: case XPathNodeType.Comment: case XPathNodeType.ProcessingInstruction: return node.Value; case XPathNodeType.Text: case XPathNodeType.Whitespace: case XPathNodeType.SignificantWhitespace: string value = node.Value; for (XmlNode n = GetNextSibling (node); n != null; n = GetNextSibling (n)) { 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 == Document.NsNodeXml ? XmlnsXML : NsNode.Value; } return String.Empty; } } public override string XmlLang { get { return node.XmlLang; } } #endregion #region Methods private bool CheckNsNameAppearance (string name, string ns) { if (iteratedNsNames != null && iteratedNsNames.Contains (name)) return true; // default namespace erasure - just add name and never return this node if (ns == String.Empty) { if (iteratedNsNames == null) iteratedNsNames = new ArrayList(); else { if (iteratedNsNames.IsReadOnly) iteratedNsNames = new ArrayList(iteratedNsNames); } iteratedNsNames.Add ("xmlns"); return true; } return false; } public override XPathNavigator Clone () { XmlDocumentNavigator clone = new XmlDocumentNavigator (node); clone.nsNode = nsNode; clone.iteratedNsNames = (iteratedNsNames == null || iteratedNsNames.IsReadOnly) ? iteratedNsNames : ArrayList.ReadOnly(iteratedNsNames); return clone; } public override string GetAttribute (string localName, string namespaceURI) { if (HasAttributes) { XmlElement el = Node as XmlElement; return el != null ? el.GetAttribute (localName, namespaceURI) : String.Empty; } return String.Empty; } public override string GetNamespace (string name) { // 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 IsDescendant (XPathNavigator other) { if (NsNode != null) return false; XmlDocumentNavigator o = other as XmlDocumentNavigator; if (o == null) return false; XmlNode n = o.node.NodeType == XmlNodeType.Attribute ? ((XmlAttribute) o.node).OwnerElement : o.node.ParentNode; for (;n != null; n = n.ParentNode) if (n == node) return true; return false; } public override bool IsSamePosition (XPathNavigator other) { XmlDocumentNavigator otherDocumentNavigator = other as XmlDocumentNavigator; if (otherDocumentNavigator != null) return node == otherDocumentNavigator.node && NsNode == otherDocumentNavigator.NsNode; return false; } public override bool MoveTo (XPathNavigator other) { XmlDocumentNavigator otherDocumentNavigator = other as XmlDocumentNavigator; if (otherDocumentNavigator != null) { if (Document == otherDocumentNavigator.Document) { node = otherDocumentNavigator.node; NsNode = otherDocumentNavigator.NsNode; return true; } } return false; } public override bool MoveToAttribute (string localName, string namespaceURI) { if (HasAttributes) { XmlAttribute attr = node.Attributes [localName, namespaceURI]; if (attr != null) { node = attr; NsNode = null; return true; } } return false; } #if NET_2_0 #else public override bool MoveToFirst () { return MoveToFirstImpl (); } #endif public override bool MoveToFirstAttribute () { if (NodeType == XPathNodeType.Element) { XmlElement el = node as XmlElement; if (!el.HasAttributes) return false; 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; } public override bool MoveToFirstChild () { if (HasChildren) { XmlNode n = GetFirstChild (node); if (n == null) return false; node = n; return true; } return false; } public override bool MoveToFirstNamespace (XPathNamespaceScope namespaceScope) { if (NodeType != XPathNodeType.Element) return false; XmlElement el = node as XmlElement; do { if (el.HasAttributes) { 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 = GetParentNode (el) as XmlElement; } while (el != null); if (namespaceScope == XPathNamespaceScope.All) { if (CheckNsNameAppearance (Document.NsNodeXml.Name, Document.NsNodeXml.Value)) return false; NsNode = Document.NsNodeXml; return true; } else return false; } public override bool MoveToId (string id) { XmlElement eltNew = Document.GetElementById (id); if (eltNew == null) return false; node = eltNew; return true; } public override bool MoveToNamespace (string name) { if (name == "xml") { NsNode = Document.NsNodeXml; return true; } if (NodeType != XPathNodeType.Element) return false; XmlElement el = node as XmlElement; do { if (el.HasAttributes) { 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 = GetParentNode (node) as XmlElement; } while (el != null); return false; } public override bool MoveToNext () { if (NsNode != null) return false; XmlNode n = node; if (NodeType == XPathNodeType.Text) { do { n = GetNextSibling (n); if (n == null) return false; switch (n.NodeType) { case XmlNodeType.CDATA: case XmlNodeType.SignificantWhitespace: case XmlNodeType.Text: case XmlNodeType.Whitespace: continue; default: break; } break; } while (true); } else n = GetNextSibling (n); if (n == null) return false; node = n; return true; } public override bool MoveToNextAttribute () { 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; } public override bool MoveToNextNamespace (XPathNamespaceScope namespaceScope) { if (NsNode == Document.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 = GetParentNode (owner) as XmlElement; while (owner != null) { if (owner.HasAttributes) { 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 = GetParentNode (owner) as XmlElement; } if (namespaceScope == XPathNamespaceScope.All) { if (CheckNsNameAppearance (Document.NsNodeXml.Name, Document.NsNodeXml.Value)) return false; NsNode = Document.NsNodeXml; return true; } return false; } public override bool MoveToParent () { 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 return false; } XmlNode n = GetParentNode (node); if (n == null) return false; node = n; NsNode = null; return true; } public override bool MoveToPrevious () { if (NsNode != null) return false; XmlNode p = GetPreviousSibling (node); if (p == null) return false; node = p; return true; } public override void MoveToRoot () { XmlAttribute attr = node as XmlAttribute; XmlNode tmp = attr != null ? attr.OwnerElement : node; if (tmp == null) return; // i.e. attr has null OwnerElement. for (XmlNode tmp2 = GetParentNode (tmp); tmp2 != null; tmp2 = GetParentNode (tmp2)) tmp = tmp2; node = tmp; NsNode = null; } private XmlNode Node { get { return NsNode != null ? NsNode : node; } } XmlNode IHasXmlNode.GetNode () { return Node; } private XmlNode GetFirstChild (XmlNode n) { if (n.FirstChild == null) return null; switch (n.FirstChild.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.DocumentType: return GetNextSibling (n.FirstChild); case XmlNodeType.EntityReference: foreach (XmlNode c in n.ChildNodes) { if (c.NodeType == XmlNodeType.EntityReference) { XmlNode ec = GetFirstChild (c); if (ec != null) return ec; } else return c; } return null; default: return n.FirstChild; } } private XmlNode GetLastChild (XmlNode n) { if (n.LastChild == null) return null; switch (n.LastChild.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.DocumentType: return GetPreviousSibling (n.LastChild); case XmlNodeType.EntityReference: for (XmlNode c = n.LastChild; c != null; c = c.PreviousSibling) { if (c.NodeType == XmlNodeType.EntityReference) { XmlNode ec = GetLastChild (c); if (ec != null) return ec; } else return c; } return null; default: return n.LastChild; } } private XmlNode GetPreviousSibling (XmlNode n) { XmlNode p = n.PreviousSibling; if (p != null) { switch (p.NodeType) { case XmlNodeType.EntityReference: XmlNode c = GetLastChild (p); if (c != null) return c; else // empty entity reference etc. return GetPreviousSibling (p); case XmlNodeType.XmlDeclaration: case XmlNodeType.DocumentType: return GetPreviousSibling (p); default: return p; } } else { if (n.ParentNode == null || n.ParentNode.NodeType != XmlNodeType.EntityReference) return null; return GetPreviousSibling (n.ParentNode); } } private XmlNode GetNextSibling (XmlNode n) { XmlNode nx = n.NextSibling; if (nx != null) { switch (nx.NodeType) { case XmlNodeType.EntityReference: XmlNode c = GetFirstChild (nx); if (c != null) return c; else // empty entity reference etc. return GetNextSibling (nx); case XmlNodeType.XmlDeclaration: case XmlNodeType.DocumentType: return GetNextSibling (nx); default: return n.NextSibling; } } else { if (n.ParentNode == null || n.ParentNode.NodeType != XmlNodeType.EntityReference) return null; return GetNextSibling (n.ParentNode); } } private XmlNode GetParentNode (XmlNode n) { if (n.ParentNode == null) return null; for (XmlNode p = n.ParentNode; p != null; p = p.ParentNode) if (p.NodeType != XmlNodeType.EntityReference) return p; return null; } #if NET_2_0 public #else internal #endif override string LookupNamespace (string prefix) { // FIXME: optimize return base.LookupNamespace (prefix); } #if NET_2_0 public #else internal #endif override string LookupPrefix (string namespaceUri) { // FIXME: optimize return base.LookupPrefix (namespaceUri); } #if NET_2_0 public #else internal #endif override bool MoveToChild (XPathNodeType type) { // FIXME: optimize return base.MoveToChild (type); } #if NET_2_0 public #else internal #endif override bool MoveToChild (string localName, string namespaceURI) { // FIXME: optimize return base.MoveToChild (localName, namespaceURI); } #if NET_2_0 public #else internal #endif override bool MoveToNext (string localName, string namespaceURI) { // FIXME: optimize return base.MoveToNext (localName, namespaceURI); } #if NET_2_0 public #else internal #endif override bool MoveToNext (XPathNodeType type) { // FIXME: optimize return base.MoveToNext (type); } #if NET_2_0 public #else internal #endif override bool MoveToFollowing (string localName, string namespaceURI, XPathNavigator end) { // FIXME: optimize return base.MoveToFollowing (localName, namespaceURI, end); } #if NET_2_0 public #else internal #endif override bool MoveToFollowing (XPathNodeType type, XPathNavigator end) { // FIXME: optimize return base.MoveToFollowing (type, end); } #endregion } }