using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Linq; namespace System.Xml.XPath { internal class XNodeNavigator : XPathNavigator, IXmlLineInfo { const int DocumentContentMask = (1 << (int)XmlNodeType.Element) | (1 << (int)XmlNodeType.ProcessingInstruction) | (1 << (int)XmlNodeType.Comment); static readonly int[] ElementContentMasks = { 0, // Root (1 << (int)XmlNodeType.Element), // Element 0, // Attribute 0, // Namespace (1 << (int)XmlNodeType.CDATA) | (1 << (int)XmlNodeType.Text), // Text 0, // SignificantWhitespace 0, // Whitespace (1 << (int)XmlNodeType.ProcessingInstruction), // ProcessingInstruction (1 << (int)XmlNodeType.Comment), // Comment (1 << (int)XmlNodeType.Element) | (1 << (int)XmlNodeType.CDATA) | (1 << (int)XmlNodeType.Text) | (1 << (int)XmlNodeType.ProcessingInstruction) | (1 << (int)XmlNodeType.Comment) // All }; new const int TextMask = (1 << (int)XmlNodeType.CDATA) | (1 << (int)XmlNodeType.Text); static XAttribute XmlNamespaceDeclaration; // The navigator position is encoded by the tuple (source, parent). // Lazy text uses (instance, parent element). Namespace declaration uses // (instance, parent element). Common XObjects uses (instance, null). object source; XElement parent; XmlNameTable nameTable; public XNodeNavigator(XNode node, XmlNameTable nameTable) { this.source = node; this.nameTable = nameTable != null ? nameTable : CreateNameTable(); } public XNodeNavigator(XNodeNavigator other) { source = other.source; parent = other.parent; nameTable = other.nameTable; } public override string BaseURI { get { XObject o = source as XObject; if (o != null) { return o.BaseUri; } if (parent != null) { return parent.BaseUri; } return string.Empty; } } public override bool HasAttributes { get { XElement e = source as XElement; if (e != null) { XAttribute a = e.lastAttr; if (a != null) { do { a = a.next; if (!a.IsNamespaceDeclaration) { return true; } } while (a != e.lastAttr); } } return false; } } public override bool HasChildren { get { XContainer c = source as XContainer; if (c != null && c.content != null) { XNode n = c.content as XNode; if (n != null) { do { n = n.next; if (IsContent(c, n)) { return true; } } while (n != c.content); return false; } string s = (string)c.content; if (s.Length != 0 && (c.parent != null || c is XElement)) { return true; } } return false; } } public override bool IsEmptyElement { get { XElement e = source as XElement; return e != null && e.IsEmpty; } } public override string LocalName { get { return nameTable.Add(GetLocalName()); } } string GetLocalName() { XElement e = source as XElement; if (e != null) { return e.Name.LocalName; } XAttribute a = source as XAttribute; if (a != null) { if (parent != null && a.Name.NamespaceName.Length == 0) { return string.Empty; // backcompat } return a.Name.LocalName; } XProcessingInstruction p = source as XProcessingInstruction; if (p != null) { return p.Target; } return string.Empty; } public override string Name { get { string prefix = GetPrefix(); if (prefix.Length == 0) { return nameTable.Add(GetLocalName()); } return nameTable.Add(string.Concat(prefix, ":", GetLocalName())); } } public override string NamespaceURI { get { return nameTable.Add(GetNamespaceURI()); } } string GetNamespaceURI() { XElement e = source as XElement; if (e != null) { return e.Name.NamespaceName; } XAttribute a = source as XAttribute; if (a != null) { if (parent != null) { return string.Empty; // backcompat } return a.Name.NamespaceName; } return string.Empty; } public override XmlNameTable NameTable { get { return nameTable; } } public override XPathNodeType NodeType { get { XObject o = source as XObject; if (o != null) { switch (o.NodeType) { case XmlNodeType.Element: return XPathNodeType.Element; case XmlNodeType.Attribute: if (parent != null) { return XPathNodeType.Namespace; } return XPathNodeType.Attribute; case XmlNodeType.Document: return XPathNodeType.Root; case XmlNodeType.Comment: return XPathNodeType.Comment; case XmlNodeType.ProcessingInstruction: return XPathNodeType.ProcessingInstruction; default: return XPathNodeType.Text; } } return XPathNodeType.Text; } } public override string Prefix { get { return nameTable.Add(GetPrefix()); } } string GetPrefix() { XElement e = source as XElement; if (e != null) { string prefix = e.GetPrefixOfNamespace(e.Name.Namespace); if (prefix != null) { return prefix; } return string.Empty; } XAttribute a = source as XAttribute; if (a != null) { if (parent != null) { return string.Empty; // backcompat } string prefix = a.GetPrefixOfNamespace(a.Name.Namespace); if (prefix != null) { return prefix; } } return string.Empty; } public override object UnderlyingObject { get { if (source is string) { // convert lazy text to eager text source = parent.LastNode; parent = null; } return source; } } public override string Value { get { XObject o = source as XObject; if (o != null) { switch (o.NodeType) { case XmlNodeType.Element: return ((XElement)o).Value; case XmlNodeType.Attribute: return ((XAttribute)o).Value; case XmlNodeType.Document: XElement root = ((XDocument)o).Root; return root != null ? root.Value : string.Empty; case XmlNodeType.Text: case XmlNodeType.CDATA: return CollectText((XText)o); case XmlNodeType.Comment: return ((XComment)o).Value; case XmlNodeType.ProcessingInstruction: return ((XProcessingInstruction)o).Data; default: return string.Empty; } } return (string)source; } } public override bool CheckValidity(System.Xml.Schema.XmlSchemaSet schemas, System.Xml.Schema.ValidationEventHandler validationEventHandler) { throw new NotSupportedException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.NotSupported_CheckValidity)); } public override XPathNavigator Clone() { return new XNodeNavigator(this); } public override bool IsSamePosition(XPathNavigator navigator) { XNodeNavigator other = navigator as XNodeNavigator; if (other == null) { return false; } return IsSamePosition(this, other); } public override bool MoveTo(XPathNavigator navigator) { XNodeNavigator other = navigator as XNodeNavigator; if (other != null) { source = other.source; parent = other.parent; return true; } return false; } public override bool MoveToAttribute(string localName, string namespaceName) { XElement e = source as XElement; if (e != null) { XAttribute a = e.lastAttr; if (a != null) { do { a = a.next; if (a.Name.LocalName == localName && a.Name.NamespaceName == namespaceName && !a.IsNamespaceDeclaration) { source = a; return true; } } while (a != e.lastAttr); } } return false; } public override bool MoveToChild(string localName, string namespaceName) { XContainer c = source as XContainer; if (c != null && c.content != null) { XNode n = c.content as XNode; if (n != null) { do { n = n.next; XElement e = n as XElement; if (e != null && e.Name.LocalName == localName && e.Name.NamespaceName == namespaceName) { source = e; return true; } } while (n != c.content); } } return false; } public override bool MoveToChild(XPathNodeType type) { XContainer c = source as XContainer; if (c != null && c.content != null) { XNode n = c.content as XNode; if (n != null) { int mask = GetElementContentMask(type); if ((TextMask & mask) != 0 && c.parent == null && c is XDocument) { mask &= ~TextMask; } do { n = n.next; if (((1 << (int)n.NodeType) & mask) != 0) { source = n; return true; } } while (n != c.content); return false; } string s = (string)c.content; if (s.Length != 0) { int mask = GetElementContentMask(type); if ((TextMask & mask) != 0 && c.parent == null && c is XDocument) { return false; } if (((1 << (int)XmlNodeType.Text) & mask) != 0) { source = s; parent = (XElement)c; return true; } } } return false; } public override bool MoveToFirstAttribute() { XElement e = source as XElement; if (e != null) { XAttribute a = e.lastAttr; if (a != null) { do { a = a.next; if (!a.IsNamespaceDeclaration) { source = a; return true; } } while (a != e.lastAttr); } } return false; } public override bool MoveToFirstChild() { XContainer c = source as XContainer; if (c != null && c.content != null) { XNode n = c.content as XNode; if (n != null) { do { n = n.next; if (IsContent(c, n)) { source = n; return true; } } while (n != c.content); return false; } string s = (string)c.content; if (s.Length != 0 && (c.parent != null || c is XElement)) { source = s; parent = (XElement)c; return true; } } return false; } public override bool MoveToFirstNamespace(XPathNamespaceScope scope) { XElement e = source as XElement; if (e != null) { XAttribute a = null; switch (scope) { case XPathNamespaceScope.Local: a = GetFirstNamespaceDeclarationLocal(e); break; case XPathNamespaceScope.ExcludeXml: a = GetFirstNamespaceDeclarationGlobal(e); while (a != null && a.Name.LocalName == "xml") { a = GetNextNamespaceDeclarationGlobal(a); } break; case XPathNamespaceScope.All: a = GetFirstNamespaceDeclarationGlobal(e); if (a == null) { a = GetXmlNamespaceDeclaration(); } break; } if (a != null) { source = a; parent = e; return true; } } return false; } public override bool MoveToId(string id) { throw new NotSupportedException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.NotSupported_MoveToId)); } public override bool MoveToNamespace(string localName) { XElement e = source as XElement; if (e != null) { if (localName == "xmlns") { return false; // backcompat } if (localName != null && localName.Length == 0) { localName = "xmlns"; // backcompat } XAttribute a = GetFirstNamespaceDeclarationGlobal(e); while (a != null) { if (a.Name.LocalName == localName) { source = a; parent = e; return true; } a = GetNextNamespaceDeclarationGlobal(a); } if (localName == "xml") { source = GetXmlNamespaceDeclaration(); parent = e; return true; } } return false; } public override bool MoveToNext() { XNode n = source as XNode; if (n != null) { XContainer c = n.parent; if (c != null && n != c.content) { do { XNode next = n.next; if (IsContent(c, next) && !(n is XText && next is XText)) { source = next; return true; } n = next; } while (n != c.content); } } return false; } public override bool MoveToNext(string localName, string namespaceName) { XNode n = source as XNode; if (n != null) { XContainer c = n.parent; if (c != null && n != c.content) { do { n = n.next; XElement e = n as XElement; if (e != null && e.Name.LocalName == localName && e.Name.NamespaceName == namespaceName) { source = e; return true; } } while (n != c.content); } } return false; } public override bool MoveToNext(XPathNodeType type) { XNode n = source as XNode; if (n != null) { XContainer c = n.parent; if (c != null && n != c.content) { int mask = GetElementContentMask(type); if ((TextMask & mask) != 0 && c.parent == null && c is XDocument) { mask &= ~TextMask; } do { XNode next = n.next; if (((1 << (int)next.NodeType) & mask) != 0 && !(n is XText && next is XText)) { source = next; return true; } n = next; } while (n != c.content); } } return false; } public override bool MoveToNextAttribute() { XAttribute a = source as XAttribute; if (a != null && parent == null) { XElement e = (XElement)a.parent; if (e != null) { while (a != e.lastAttr) { a = a.next; if (!a.IsNamespaceDeclaration) { source = a; return true; } } } } return false; } public override bool MoveToNextNamespace(XPathNamespaceScope scope) { XAttribute a = source as XAttribute; if (a != null && parent != null && !IsXmlNamespaceDeclaration(a)) { switch (scope) { case XPathNamespaceScope.Local: if (a.parent != parent) { return false; } a = GetNextNamespaceDeclarationLocal(a); break; case XPathNamespaceScope.ExcludeXml: do { a = GetNextNamespaceDeclarationGlobal(a); } while (a != null && (a.Name.LocalName == "xml" || HasNamespaceDeclarationInScope(a, parent))); break; case XPathNamespaceScope.All: do { a = GetNextNamespaceDeclarationGlobal(a); } while (a != null && HasNamespaceDeclarationInScope(a, parent)); if (a == null && !HasNamespaceDeclarationInScope(GetXmlNamespaceDeclaration(), parent)) { a = GetXmlNamespaceDeclaration(); } break; } if (a != null) { source = a; return true; } } return false; } public override bool MoveToParent() { if (parent != null) { source = parent; parent = null; return true; } XObject o = (XObject)source; if (o.parent != null) { source = o.parent; return true; } return false; } public override bool MoveToPrevious() { XNode n = source as XNode; if (n != null) { XContainer c = n.parent; if (c != null) { XNode q = (XNode)c.content; if (q.next != n) { XNode p = null; do { q = q.next; if (IsContent(c, q)) { p = p is XText && q is XText ? p : q; } } while (q.next != n); if (p != null) { source = p; return true; } } } } return false; } public override XmlReader ReadSubtree() { XContainer c = source as XContainer; if (c == null) throw new InvalidOperationException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.InvalidOperation_BadNodeType, NodeType)); return new XNodeReader(c, nameTable); } bool IXmlLineInfo.HasLineInfo() { IXmlLineInfo li = source as IXmlLineInfo; if (li != null) { return li.HasLineInfo(); } return false; } int IXmlLineInfo.LineNumber { get { IXmlLineInfo li = source as IXmlLineInfo; if (li != null) { return li.LineNumber; } return 0; } } int IXmlLineInfo.LinePosition { get { IXmlLineInfo li = source as IXmlLineInfo; if (li != null) { return li.LinePosition; } return 0; } } static string CollectText(XText n) { string s = n.Value; if (n.parent != null) { while (n != n.parent.content) { n = n.next as XText; if (n == null) break; s += n.Value; } } return s; } static XmlNameTable CreateNameTable() { XmlNameTable nameTable = new NameTable(); nameTable.Add(string.Empty); nameTable.Add(XNamespace.xmlnsPrefixNamespace); nameTable.Add(XNamespace.xmlPrefixNamespace); return nameTable; } static bool IsContent(XContainer c, XNode n) { if (c.parent != null || c is XElement) { return true; } return ((1 << (int)n.NodeType) & DocumentContentMask) != 0; } static bool IsSamePosition(XNodeNavigator n1, XNodeNavigator n2) { if (n1.source == n2.source && n1.parent == n2.parent) { return true; } // compare lazy text with eager text if (n1.parent != null ^ n2.parent != null) { XText t1 = n1.source as XText; if (t1 != null) { return (object)t1.Value == (object)n2.source && t1.parent == n2.parent; } XText t2 = n2.source as XText; if (t2 != null) { return (object)t2.Value == (object)n1.source && t2.parent == n1.parent; } } return false; } static bool IsXmlNamespaceDeclaration(XAttribute a) { return (object)a == (object)GetXmlNamespaceDeclaration(); } static int GetElementContentMask(XPathNodeType type) { return ElementContentMasks[(int)type]; } static XAttribute GetFirstNamespaceDeclarationGlobal(XElement e) { do { XAttribute a = GetFirstNamespaceDeclarationLocal(e); if (a != null) { return a; } e = e.parent as XElement; } while (e != null); return null; } static XAttribute GetFirstNamespaceDeclarationLocal(XElement e) { XAttribute a = e.lastAttr; if (a != null) { do { a = a.next; if (a.IsNamespaceDeclaration) { return a; } } while (a != e.lastAttr); } return null; } static XAttribute GetNextNamespaceDeclarationGlobal(XAttribute a) { XElement e = (XElement)a.parent; if (e == null) { return null; } XAttribute next = GetNextNamespaceDeclarationLocal(a); if (next != null) { return next; } e = e.parent as XElement; if (e == null) { return null; } return GetFirstNamespaceDeclarationGlobal(e); } static XAttribute GetNextNamespaceDeclarationLocal(XAttribute a) { XElement e = (XElement)a.parent; if (e == null) { return null; } while (a != e.lastAttr) { a = a.next; if (a.IsNamespaceDeclaration) { return a; } } return null; } static XAttribute GetXmlNamespaceDeclaration() { if (XmlNamespaceDeclaration == null) { System.Threading.Interlocked.CompareExchange(ref XmlNamespaceDeclaration, new XAttribute(XNamespace.Xmlns.GetName("xml"), XNamespace.xmlPrefixNamespace), null); } return XmlNamespaceDeclaration; } static bool HasNamespaceDeclarationInScope(XAttribute a, XElement e) { XName name = a.Name; while (e != null && e != a.parent) { if (e.Attribute(name) != null) { return true; } e = e.parent as XElement; } return false; } } struct XPathEvaluator { public object Evaluate(XNode node, string expression, IXmlNamespaceResolver resolver) where T : class { XPathNavigator navigator = node.CreateNavigator(); object result = navigator.Evaluate(expression, resolver); if (result is XPathNodeIterator) { return EvaluateIterator((XPathNodeIterator)result); } if (!(result is T)) throw new InvalidOperationException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.InvalidOperation_UnexpectedEvaluation, result.GetType())); return (T)result; } IEnumerable EvaluateIterator(XPathNodeIterator result) { foreach (XPathNavigator navigator in result) { object r = navigator.UnderlyingObject; if (!(r is T)) throw new InvalidOperationException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.InvalidOperation_UnexpectedEvaluation, r.GetType())); yield return (T)r; XText t = r as XText; if (t != null && t.parent != null) { while (t != t.parent.content) { t = t.next as XText; if (t == null) break; yield return (T)(object)t; } } } } } /// /// Extension methods /// public static class Extensions { /// /// Creates an for a given /// /// Extension point /// An public static XPathNavigator CreateNavigator(this XNode node) { return node.CreateNavigator(null); } /// /// Creates an for a given /// /// Extension point /// The to be used by /// the /// An public static XPathNavigator CreateNavigator(this XNode node, XmlNameTable nameTable) { if (node == null) throw new ArgumentNullException("node"); if (node is XDocumentType) throw new ArgumentException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.Argument_CreateNavigator, XmlNodeType.DocumentType)); XText text = node as XText; if (text != null) { if (text.parent is XDocument) throw new ArgumentException(System.Xml.Linq.Res.GetString(System.Xml.Linq.Res.Argument_CreateNavigator, XmlNodeType.Whitespace)); node = CalibrateText(text); } return new XNodeNavigator(node, nameTable); } /// /// Evaluates an XPath expression /// /// Extension point /// The XPath expression /// The result of evaluating the expression which can be typed as bool, double, string or /// IEnumerable public static object XPathEvaluate(this XNode node, string expression) { return node.XPathEvaluate(expression, null); } /// /// Evaluates an XPath expression /// /// Extension point /// The XPath expression /// A for the namespace /// prefixes used in the XPath expression /// The result of evaluating the expression which can be typed as bool, double, string or /// IEnumerable public static object XPathEvaluate(this XNode node, string expression, IXmlNamespaceResolver resolver) { if (node == null) throw new ArgumentNullException("node"); return new XPathEvaluator().Evaluate(node, expression, resolver); } /// /// Select an using a XPath expression /// /// Extension point /// The XPath expression /// An or null public static XElement XPathSelectElement(this XNode node, string expression) { return node.XPathSelectElement(expression, null); } /// /// Select an using a XPath expression /// /// Extension point /// The XPath expression /// A for the namespace /// prefixes used in the XPath expression /// An or null public static XElement XPathSelectElement(this XNode node, string expression, IXmlNamespaceResolver resolver) { return node.XPathSelectElements(expression, resolver).FirstOrDefault(); } /// /// Select a set of using a XPath expression /// /// Extension point /// The XPath expression /// An corresponding to the resulting set of elements public static IEnumerable XPathSelectElements(this XNode node, string expression) { return node.XPathSelectElements(expression, null); } /// /// Select a set of using a XPath expression /// /// Extension point /// The XPath expression /// A for the namespace /// prefixes used in the XPath expression /// An corresponding to the resulting set of elements public static IEnumerable XPathSelectElements(this XNode node, string expression, IXmlNamespaceResolver resolver) { if (node == null) throw new ArgumentNullException("node"); return (IEnumerable)new XPathEvaluator().Evaluate(node, expression, resolver); } static XText CalibrateText(XText n) { if (n.parent == null) { return n; } XNode p = (XNode)n.parent.content; while (true) { p = p.next; XText t = p as XText; if (t != null) { do { if (p == n) { return t; } p = p.next; } while (p is XText); } } } } }