Get the XPath to an XElement?
The extensions methods:
public static class XExtensions{ /// <summary> /// Get the absolute XPath to a given XElement /// (e.g. "/people/person[6]/name[1]/last[1]"). /// </summary> public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func<XElement, string> relativeXPath = e => { int index = e.IndexPosition(); string name = e.Name.LocalName; // If the element is the root, no index is required return (index == -1) ? "/" + name : string.Format ( "/{0}[{1}]", name, index.ToString() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(element); } /// <summary> /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. /// </summary> /// <param name="element"> /// The element to get the index of. /// </param> public static int IndexPosition(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } if (element.Parent == null) { return -1; } int i = 1; // Indexes for nodes start at 1, not 0 foreach (var sibling in element.Parent.Elements(element.Name)) { if (sibling == element) { return i; } i++; } throw new InvalidOperationException ("element has been removed from its parent."); }}
And the test:
class Program{ static void Main(string[] args) { Program.Process(XDocument.Load(@"C:\test.xml").Root); Console.Read(); } static void Process(XElement element) { if (!element.HasElements) { Console.WriteLine(element.GetAbsoluteXPath()); } else { foreach (XElement child in element.Elements()) { Process(child); } } }}
And sample output:
/tests/test[1]/date[1]/tests/test[1]/time[1]/start[1]/tests/test[1]/time[1]/end[1]/tests/test[1]/facility[1]/name[1]/tests/test[1]/facility[1]/website[1]/tests/test[1]/facility[1]/street[1]/tests/test[1]/facility[1]/state[1]/tests/test[1]/facility[1]/city[1]/tests/test[1]/facility[1]/zip[1]/tests/test[1]/facility[1]/phone[1]/tests/test[1]/info[1]/tests/test[2]/date[1]/tests/test[2]/time[1]/start[1]/tests/test[2]/time[1]/end[1]/tests/test[2]/facility[1]/name[1]/tests/test[2]/facility[1]/website[1]/tests/test[2]/facility[1]/street[1]/tests/test[2]/facility[1]/state[1]/tests/test[2]/facility[1]/city[1]/tests/test[2]/facility[1]/zip[1]/tests/test[2]/facility[1]/phone[1]/tests/test[2]/info[1]
That should settle this. No?
I updated the code by Chris to take into account namespace prefixes. Only the GetAbsoluteXPath method is modified.
public static class XExtensions{ /// <summary> /// Get the absolute XPath to a given XElement, including the namespace. /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]"). /// </summary> public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func<XElement, string> relativeXPath = e => { int index = e.IndexPosition(); var currentNamespace = e.Name.Namespace; string name; if (currentNamespace == null) { name = e.Name.LocalName; } else { string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace); name = namespacePrefix + ":" + e.Name.LocalName; } // If the element is the root, no index is required return (index == -1) ? "/" + name : string.Format ( "/{0}[{1}]", name, index.ToString() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(element); } /// <summary> /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. /// </summary> /// <param name="element"> /// The element to get the index of. /// </param> public static int IndexPosition(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } if (element.Parent == null) { return -1; } int i = 1; // Indexes for nodes start at 1, not 0 foreach (var sibling in element.Parent.Elements(element.Name)) { if (sibling == element) { return i; } i++; } throw new InvalidOperationException ("element has been removed from its parent."); }}
Let me share my latest modification to this class.Basicaly it excludes index if element has no sibling and includes namespaces with local-name() operator has i was having issues with the namespace prefix.
public static class XExtensions{ /// <summary> /// Get the absolute XPath to a given XElement, including the namespace. /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]"). /// </summary> public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func<XElement, string> relativeXPath = e => { int index = e.IndexPosition(); var currentNamespace = e.Name.Namespace; string name; if (String.IsNullOrEmpty(currentNamespace.ToString())) { name = e.Name.LocalName; } else { name = "*[local-name()='" + e.Name.LocalName + "']"; //string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace); //name = namespacePrefix + ":" + e.Name.LocalName; } // If the element is the root or has no sibling elements, no index is required return ((index == -1) || (index == -2)) ? "/" + name : string.Format ( "/{0}[{1}]", name, index.ToString() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(element); } /// <summary> /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned or -2 if element has no sibling elements. /// </summary> /// <param name="element"> /// The element to get the index of. /// </param> public static int IndexPosition(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } if (element.Parent == null) { // Element is root return -1; } if (element.Parent.Elements(element.Name).Count() == 1) { // Element has no sibling elements return -2; } int i = 1; // Indexes for nodes start at 1, not 0 foreach (var sibling in element.Parent.Elements(element.Name)) { if (sibling == element) { return i; } i++; } throw new InvalidOperationException ("element has been removed from its parent."); }}