|
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<T>(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<T>((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<T> EvaluateIterator<T>(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;
}
}
}
}
}
/// <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) {
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);
}
/// <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) {
if (node == null) throw new ArgumentNullException("node");
return new 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) {
if (node == null) throw new ArgumentNullException("node");
return (IEnumerable<XElement>)new XPathEvaluator().Evaluate<XElement>(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);
}
}
}
}
}
|