C# 获取 XElement 的 XPath?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/451950/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get the XPath to an XElement?
提问by core
I've got an XElement deep within a document. Given the XElement (and XDocument?), is there an extension method to get its full (i.e. absolute, e.g. /root/item/element/child
) XPath?
我在文档深处有一个 XElement。鉴于 XElement(和 XDocument?),是否有扩展方法来获取其完整(即绝对,例如/root/item/element/child
)XPath?
E.g. myXElement.GetXPath()?
例如 myXElement.GetXPath()?
EDIT: Okay, looks like I overlooked something very important. Whoops! The index of the element needs to be taken into account. See my last answer for the proposed corrected solution.
编辑:好的,看起来我忽略了一些非常重要的事情。哎呀!需要考虑元素的索引。有关建议的更正解决方案,请参阅我的最后一个答案。
采纳答案by core
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?
那应该解决这个问题。不?
回答by Scott Dorman
If you're looking for something natively provided by .NET the answer is no. You would have to write your own extension method to do this.
如果您正在寻找 .NET 本机提供的东西,答案是否定的。您必须编写自己的扩展方法来执行此操作。
回答by Rune Grimstad
There can be several xpaths that lead to the same element, so finding the simplest xpath that leads to the node is not trivial.
可以有多个 xpath 通向同一个元素,因此找到通向该节点的最简单的 xpath 并非易事。
That said, it is quite easy to find an xpath to the node. Just step up the node tree until you read the root node and combine the node names and you have a valid xpath.
也就是说,很容易找到节点的 xpath。只需逐步向上节点树,直到您读取根节点并组合节点名称并且您拥有有效的 xpath。
回答by annakata
By "full xpath" I assume you mean a simple chain of tags since the number of xpaths which could potentially match any element could be verylarge.
我认为“完整的 xpath”是指一个简单的标签链,因为可能匹配任何元素的 xpath 的数量可能非常大。
The problem here is that it's very hard if not specifically impossible to build any given xpath which will reversibly trace back to the same element - is that a condition?
这里的问题是,如果不是特别不可能构建任何可以可逆地追溯到同一元素的给定 xpath,那是非常困难的——这是一个条件吗?
If "no" then perhaps you could build a query by recursively looping with reference to the current elements parentNode. If "yes", then you're going to be looking at extending that by cross referencing for index position within sibling sets, referecing ID-like attributes if they exist, and this is going to be very dependant on your XSD if a general solution is possible.
如果“否”,那么也许您可以通过参考当前元素 parentNode 递归循环来构建查询。如果“是”,那么您将考虑通过交叉引用兄弟集中的索引位置来扩展它,引用类似 ID 的属性(如果它们存在),如果通用解决方案,这将非常依赖于您的 XSD是可能的。
回答by Robert Rossney
This is actually a duplicate of thisquestion. While it's not marked as the answer, the method in my answerto that question is the only way of unambiguously formulating the XPath to a node within an XML document that will always work under all circumstances. (It also works for all node types, not just elements.)
这实际上是这个问题的重复。虽然它没有标记为答案,但我对该问题的回答中的方法是将 XPath 明确地表述为 XML 文档中的节点的唯一方法,该节点在所有情况下都将始终有效。(它也适用于所有节点类型,而不仅仅是元素。)
As you can see, the XPath it produces is ugly and abstract. but it addresses the concerns that many answerers have raised here. Most of the suggestions made here produce an XPath that, when used to search the original document, will produce a set of one or more nodes that includes the target node. It's that "or more" that's the problem. For instance, if I have an XML representation of a DataSet, the naive XPath to a specific DataRow's element, /DataSet1/DataTable1
, also returns the elements of all of the other DataRows in the DataTable. You can't disambiguate that without knowing something about how the XML is forumlated (like, is there a primary-key element?).
如您所见,它生成的 XPath 既丑陋又抽象。但它解决了许多回答者在这里提出的问题。这里提出的大多数建议都会生成一个 XPath,当用于搜索原始文档时,它会生成一组一个或多个包含目标节点的节点。问题在于“或更多”。例如,如果我有一个 DataSet 的 XML 表示,那么特定 DataRow 元素的原始 XPath/DataSet1/DataTable1
也会返回 DataTable 中所有其他DataRow 的元素。如果不了解 XML 是如何论坛化的(例如,是否有主键元素?),您就无法消除歧义。
But /node()[1]/node()[4]/node()[11]
, there's only one node that it'll ever return, no matter what.
但是/node()[1]/node()[4]/node()[11]
,无论如何,它只会返回一个节点。
回答by Bernard Vander Beken
I updated the code by Chris to take into account namespace prefixes. Only the GetAbsoluteXPath method is modified.
我更新了 Chris 的代码以考虑名称空间前缀。仅修改了 GetAbsoluteXPath 方法。
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.");
}
}
回答by Eli Algranti
As part of a different projectI developed an extension method to generate a simple XPath to an element. It is similar to the selected answer, but supports XAttribute, XText, XCData and XComment in addition to XElement. It's available as code nuget, project page here: xmlspecificationcompare.codeplex.com
作为另一个项目的一部分,我开发了一种扩展方法来生成元素的简单 XPath。它类似于选定的答案,但除了 XElement 之外还支持 XAttribute、XText、XCData 和 XComment。它可以作为代码 nuget 使用,项目页面在这里:xmlspecificationcompare.codeplex.com
回答by Chaveiro
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.
让我分享我对这个类的最新修改。基本上它排除索引,如果元素没有兄弟元素并且包含带有 local-name() 运算符的命名空间,我的命名空间前缀有问题。
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.");
}
}
回答by hillin
Microsoft has provided an extension method to do this since .NET Framework 3.5:
自 .NET Framework 3.5 起,Microsoft 提供了一种扩展方法来执行此操作:
http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx
http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx
Just add a using to System.Xml.XPath
and invoke the following methods:
只需添加一个 usingSystem.Xml.XPath
并调用以下方法:
XPathSelectElement
: select a single ElementXPathSelectElements
: select elements and return as anIEnumerable<XElement>
XPathEvaluate
: select nodes (not only elements, but also text, comments etc.) and return as anIEnumerable<object>
XPathSelectElement
: 选择单个元素XPathSelectElements
: 选择元素并作为一个返回IEnumerable<XElement>
XPathEvaluate
: 选择节点(不仅是元素,还有文本、评论等)并作为一个返回IEnumerable<object>