|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
namespace System.Xml.XPath
{
internal sealed class XNodeNavigator : XPathNavigator, IXmlLineInfo
{
internal static readonly string xmlPrefixNamespace = XNamespace.Xml.NamespaceName;
internal static readonly string xmlnsPrefixNamespace = XNamespace.Xmlns.NamespaceName;
private const int DocumentContentMask =
(1 << (int)XmlNodeType.Element) |
(1 << (int)XmlNodeType.ProcessingInstruction) |
(1 << (int)XmlNodeType.Comment);
private static ReadOnlySpan<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
];
private const int TextMask =
(1 << (int)XmlNodeType.CDATA) |
(1 << (int)XmlNodeType.Text);
private static XAttribute? s_XmlNamespaceDeclaration;
// The navigator position is encoded by the tuple (source, parent).
// Namespace declaration uses (instance, parent element).
// Common XObjects uses (instance, null).
private XObject _source;
private XElement? _parent;
private readonly XmlNameTable _nameTable;
public XNodeNavigator(XNode node, XmlNameTable? nameTable)
{
_source = node;
_nameTable = nameTable ?? CreateNameTable();
}
public XNodeNavigator(XNodeNavigator other)
{
_source = other._source;
_parent = other._parent;
_nameTable = other._nameTable;
}
public override string BaseURI
{
get
{
if (_source != null)
{
return _source.BaseUri;
}
if (_parent != null)
{
return _parent.BaseUri;
}
return string.Empty;
}
}
public override bool HasAttributes
{
get
{
XElement? element = _source as XElement;
if (element != null)
{
foreach (XAttribute attribute in element.Attributes())
{
if (!attribute.IsNamespaceDeclaration)
{
return true;
}
}
}
return false;
}
}
public override bool HasChildren
{
get
{
XContainer? container = _source as XContainer;
if (container != null)
{
foreach (XNode node in container.Nodes())
{
if (IsContent(container, node))
{
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()); }
}
private 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()); }
}
private 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
{
if (_source != null)
{
switch (_source.NodeType)
{
case XmlNodeType.Element:
return XPathNodeType.Element;
case XmlNodeType.Attribute:
XAttribute attribute = (XAttribute)_source;
return attribute.IsNamespaceDeclaration ? XPathNodeType.Namespace : 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()); }
}
private 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
{
return _source;
}
}
public override string Value
{
get
{
if (_source != null)
{
switch (_source.NodeType)
{
case XmlNodeType.Element:
return ((XElement)_source).Value;
case XmlNodeType.Attribute:
return ((XAttribute)_source).Value;
case XmlNodeType.Document:
XElement? root = ((XDocument)_source).Root;
return root != null ? root.Value : string.Empty;
case XmlNodeType.Text:
case XmlNodeType.CDATA:
return CollectText((XText)_source);
case XmlNodeType.Comment:
return ((XComment)_source).Value;
case XmlNodeType.ProcessingInstruction:
return ((XProcessingInstruction)_source).Data;
default:
return string.Empty;
}
}
return string.Empty;
}
}
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)
{
foreach (XAttribute attribute in e.Attributes())
{
if (attribute.Name.LocalName == localName &&
attribute.Name.NamespaceName == namespaceName &&
!attribute.IsNamespaceDeclaration)
{
_source = attribute;
return true;
}
}
}
return false;
}
public override bool MoveToChild(string localName, string namespaceName)
{
XContainer? c = _source as XContainer;
if (c != null)
{
foreach (XElement element in c.Elements())
{
if (element.Name.LocalName == localName &&
element.Name.NamespaceName == namespaceName)
{
_source = element;
return true;
}
}
}
return false;
}
public override bool MoveToChild(XPathNodeType type)
{
XContainer? c = _source as XContainer;
if (c != null)
{
int mask = GetElementContentMask(type);
if ((TextMask & mask) != 0 && c.GetParent() == null && c is XDocument)
{
mask &= ~TextMask;
}
foreach (XNode node in c.Nodes())
{
if (((1 << (int)node.NodeType) & mask) != 0)
{
_source = node;
return true;
}
}
}
return false;
}
public override bool MoveToFirstAttribute()
{
XElement? e = _source as XElement;
if (e != null)
{
foreach (XAttribute attribute in e.Attributes())
{
if (!attribute.IsNamespaceDeclaration)
{
_source = attribute;
return true;
}
}
}
return false;
}
public override bool MoveToFirstChild()
{
XContainer? container = _source as XContainer;
if (container != null)
{
foreach (XNode node in container.Nodes())
{
if (IsContent(container, node))
{
_source = node;
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) ??
GetXmlNamespaceDeclaration();
break;
}
if (a != null)
{
_source = a;
_parent = e;
return true;
}
}
return false;
}
public override bool MoveToId(string id)
{
throw new NotSupportedException(SR.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? currentNode = _source as XNode;
if (currentNode != null)
{
XContainer? container = currentNode.GetParent();
if (container != null)
{
XNode? next;
for (XNode node = currentNode; node != null; node = next)
{
next = node.NextNode;
if (next == null)
{
break;
}
if (IsContent(container, next) && !(node is XText && next is XText))
{
_source = next;
return true;
}
}
}
}
return false;
}
public override bool MoveToNext(string localName, string namespaceName)
{
XNode? currentNode = _source as XNode;
if (currentNode != null)
{
foreach (XElement element in currentNode.ElementsAfterSelf())
{
if (element.Name.LocalName == localName &&
element.Name.NamespaceName == namespaceName)
{
_source = element;
return true;
}
}
}
return false;
}
public override bool MoveToNext(XPathNodeType type)
{
XNode? currentNode = _source as XNode;
if (currentNode != null)
{
XContainer? container = currentNode.GetParent();
if (container != null)
{
int mask = GetElementContentMask(type);
if ((TextMask & mask) != 0 && container.GetParent() == null && container is XDocument)
{
mask &= ~TextMask;
}
XNode? next;
for (XNode node = currentNode; ; node = next)
{
next = node.NextNode;
if (next == null)
{
break;
}
if (((1 << (int)next.NodeType) & mask) != 0 && !(node is XText && next is XText))
{
_source = next;
return true;
}
}
}
}
return false;
}
public override bool MoveToNextAttribute()
{
XAttribute? currentAttribute = _source as XAttribute;
if (currentAttribute != null && _parent == null)
{
XElement? e = (XElement?)currentAttribute.GetParent();
if (e != null)
{
for (XAttribute? attribute = currentAttribute.NextAttribute; attribute != null; attribute = attribute.NextAttribute)
{
if (!attribute.IsNamespaceDeclaration)
{
_source = attribute;
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.GetParent() != _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;
}
XNode? parentNode = _source.GetParent();
if (parentNode != null)
{
_source = parentNode;
return true;
}
return false;
}
public override bool MoveToPrevious()
{
XNode? currentNode = _source as XNode;
if (currentNode != null)
{
XContainer? container = currentNode.GetParent();
if (container != null)
{
XNode? previous = null;
foreach (XNode node in container.Nodes())
{
if (node == currentNode)
{
if (previous != null)
{
_source = previous;
return true;
}
return false;
}
if (IsContent(container, node))
{
previous = node;
}
}
}
}
return false;
}
public override XmlReader ReadSubtree()
{
XContainer? c = _source as XContainer;
if (c == null) throw new InvalidOperationException(SR.Format(SR.InvalidOperation_BadNodeType, NodeType));
return c.CreateReader();
}
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;
}
}
private static string CollectText(XText n)
{
string s = n.Value;
if (n.GetParent() != null)
{
foreach (XNode node in n.NodesAfterSelf())
{
XText? t = node as XText;
if (t == null) break;
s += t.Value;
}
}
return s;
}
private static NameTable CreateNameTable()
{
var nameTable = new NameTable();
nameTable.Add(string.Empty);
nameTable.Add(xmlnsPrefixNamespace);
nameTable.Add(xmlPrefixNamespace);
return nameTable;
}
private static bool IsContent(XContainer c, XNode n)
{
if (c.GetParent() != null || c is XElement)
{
return true;
}
return ((1 << (int)n.NodeType) & DocumentContentMask) != 0;
}
private static bool IsSamePosition(XNodeNavigator n1, XNodeNavigator n2)
{
return n1._source == n2._source && n1._source.GetParent() == n2._source.GetParent();
}
private static bool IsXmlNamespaceDeclaration(XAttribute a)
{
return (object)a == (object)GetXmlNamespaceDeclaration();
}
private static int GetElementContentMask(XPathNodeType type)
{
return ElementContentMasks[(int)type];
}
private static XAttribute? GetFirstNamespaceDeclarationGlobal(XElement e)
{
XElement? ce = e;
do
{
XAttribute? a = GetFirstNamespaceDeclarationLocal(ce);
if (a != null)
{
return a;
}
ce = ce.Parent;
} while (ce != null);
return null;
}
private static XAttribute? GetFirstNamespaceDeclarationLocal(XElement e)
{
foreach (XAttribute attribute in e.Attributes())
{
if (attribute.IsNamespaceDeclaration)
{
return attribute;
}
}
return null;
}
private static XAttribute? GetNextNamespaceDeclarationGlobal(XAttribute a)
{
XElement? e = (XElement?)a.GetParent();
if (e == null)
{
return null;
}
XAttribute? next = GetNextNamespaceDeclarationLocal(a);
if (next != null)
{
return next;
}
e = e.Parent;
if (e == null)
{
return null;
}
return GetFirstNamespaceDeclarationGlobal(e);
}
private static XAttribute? GetNextNamespaceDeclarationLocal(XAttribute a)
{
XElement? e = a.Parent;
if (e == null)
{
return null;
}
XAttribute? ca = a;
ca = ca.NextAttribute;
while (ca != null)
{
if (ca.IsNamespaceDeclaration)
{
return ca;
}
ca = ca.NextAttribute;
}
return null;
}
private static XAttribute GetXmlNamespaceDeclaration()
{
if (s_XmlNamespaceDeclaration == null)
{
System.Threading.Interlocked.CompareExchange(ref s_XmlNamespaceDeclaration, new XAttribute(XNamespace.Xmlns.GetName("xml"), xmlPrefixNamespace), null);
}
return s_XmlNamespaceDeclaration;
}
private static bool HasNamespaceDeclarationInScope(XAttribute a, XElement e)
{
XName name = a.Name;
XElement? ce = e;
while (ce != null && ce != a.GetParent())
{
if (ce.Attribute(name) != null)
{
return true;
}
ce = ce.Parent;
}
return false;
}
}
internal static class XPathEvaluator
{
public static object Evaluate<T>(XNode node, string expression, IXmlNamespaceResolver? resolver) where T : class
{
XPathNavigator navigator = node.CreateNavigator();
object result = navigator.Evaluate(expression, resolver);
XPathNodeIterator? iterator = result as XPathNodeIterator;
if (iterator != null)
{
return EvaluateIterator<T>(iterator);
}
if (!(result is T)) throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedEvaluation, result.GetType()));
return (T)result;
}
private static IEnumerable<T> EvaluateIterator<T>(XPathNodeIterator result)
{
foreach (XPathNavigator navigator in result)
{
Debug.Assert(navigator.UnderlyingObject != null);
object r = navigator.UnderlyingObject;
if (!(r is T)) throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedEvaluation, r.GetType()));
yield return (T)r;
XText? t = r as XText;
if (t != null && t.GetParent() != null)
{
do
{
t = t.NextNode as XText;
if (t == null) break;
yield return (T)(object)t;
} while (t != t.GetParent()!.LastNode);
}
}
}
}
/// <summary>
/// Extension methods
/// </summary>
public static class Extensions
{
/// <summary>
/// Creates an <see cref="XPathNavigator"/> for a given <see cref="XNode"/>
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <returns>An <see cref="XPathNavigator"/></returns>
public static XPathNavigator CreateNavigator(this XNode node)
{
return node.CreateNavigator(null);
}
/// <summary>
/// Creates an <see cref="XPathNavigator"/> for a given <see cref="XNode"/>
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="nameTable">The <see cref="XmlNameTable"/> to be used by
/// the <see cref="XPathNavigator"/></param>
/// <returns>An <see cref="XPathNavigator"/></returns>
public static XPathNavigator CreateNavigator(this XNode node, XmlNameTable? nameTable)
{
ArgumentNullException.ThrowIfNull(node);
if (node is XDocumentType) throw new ArgumentException(SR.Format(SR.Argument_CreateNavigator, XmlNodeType.DocumentType));
XText? text = node as XText;
if (text != null)
{
if (text.GetParent() is XDocument) throw new ArgumentException(SR.Format(SR.Argument_CreateNavigator, XmlNodeType.Whitespace));
node = CalibrateText(text);
}
return new XNodeNavigator(node, nameTable);
}
/// <summary>
/// Evaluates an XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <returns>The result of evaluating the expression which can be typed as bool, double, string or
/// IEnumerable</returns>
public static object XPathEvaluate(this XNode node, string expression)
{
return node.XPathEvaluate(expression, null);
}
/// <summary>
/// Evaluates an XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <param name="resolver">A <see cref="IXmlNamespaceResolver"> for the namespace
/// prefixes used in the XPath expression</see></param>
/// <returns>The result of evaluating the expression which can be typed as bool, double, string or
/// IEnumerable</returns>
public static object XPathEvaluate(this XNode node, string expression, IXmlNamespaceResolver? resolver)
{
ArgumentNullException.ThrowIfNull(node);
return XPathEvaluator.Evaluate<object>(node, expression, resolver);
}
/// <summary>
/// Select an <see cref="XElement"/> using a XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <returns>An <see cref="XElement"> or null</see></returns>
public static XElement? XPathSelectElement(this XNode node, string expression)
{
return node.XPathSelectElement(expression, null);
}
/// <summary>
/// Select an <see cref="XElement"/> using a XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <param name="resolver">A <see cref="IXmlNamespaceResolver"/> for the namespace
/// prefixes used in the XPath expression</param>
/// <returns>An <see cref="XElement"> or null</see></returns>
public static XElement? XPathSelectElement(this XNode node, string expression, IXmlNamespaceResolver? resolver)
{
return node.XPathSelectElements(expression, resolver).FirstOrDefault();
}
/// <summary>
/// Select a set of <see cref="XElement"/> using a XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <returns>An <see cref="IEnumerable<XElement>"/> corresponding to the resulting set of elements</returns>
public static IEnumerable<XElement> XPathSelectElements(this XNode node, string expression)
{
return node.XPathSelectElements(expression, null);
}
/// <summary>
/// Select a set of <see cref="XElement"/> using a XPath expression
/// </summary>
/// <param name="node">Extension point <see cref="XNode"/></param>
/// <param name="expression">The XPath expression</param>
/// <param name="resolver">A <see cref="IXmlNamespaceResolver"/> for the namespace
/// prefixes used in the XPath expression</param>
/// <returns>An <see cref="IEnumerable<XElement>"/> corresponding to the resulting set of elements</returns>
public static IEnumerable<XElement> XPathSelectElements(this XNode node, string expression, IXmlNamespaceResolver? resolver)
{
ArgumentNullException.ThrowIfNull(node);
return (IEnumerable<XElement>)XPathEvaluator.Evaluate<XElement>(node, expression, resolver);
}
private static XText CalibrateText(XText n)
{
XContainer? parentNode = n.GetParent();
if (parentNode == null)
{
return n;
}
foreach (XNode node in parentNode.Nodes())
{
XText? t = node as XText;
bool isTextNode = t != null;
if (isTextNode && node == n)
{
return t!;
}
}
System.Diagnostics.Debug.Fail("Parent node doesn't contain itself.");
return null;
}
}
}
|