我在文档中有一个XElement.给定XElement(和XDocument?),是否有一个扩展方法来获取其完整(即绝对,例如/root/item/element/child
)XPath?
例如myXElement.GetXPath()?
编辑:好的,看起来我忽略了一些非常重要的事情.哎呦!需要考虑元素的索引.请参阅我对修正后的解决方案的最后答案.
扩展方法:
public static class XExtensions { ////// Get the absolute XPath to a given XElement /// (e.g. "/people/person[6]/name[1]/last[1]"). /// public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } FuncrelativeXPath = 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); } /// /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. /// /// /// The element to get the index of. /// 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."); } }
而且测试:
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); } } } }
和样本输出:
/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]
那应该解决这个问题.没有?
我更新了Chris的代码,以考虑名称空间前缀.仅修改了GetAbsoluteXPath方法.
public static class XExtensions { ////// Get the absolute XPath to a given XElement, including the namespace. /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]"). /// public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } FuncrelativeXPath = 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); } /// /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. /// /// /// The element to get the index of. /// 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."); } }
让我分享对这堂课的最新修改。基本上,如果element没有兄弟姐妹,它会排除索引,并且如果我遇到名称空间前缀问题,则使用local-name()运算符包含名称空间。
public static class XExtensions { ////// Get the absolute XPath to a given XElement, including the namespace. /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]"). /// public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } FuncrelativeXPath = 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); } /// /// 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. /// /// /// The element to get the index of. /// 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."); } }