我在文档中深入了解了XElement。鉴于XElement(和XDocument?),是否有扩展方法来获取其全部(即绝对,例如)XPath?获取XPath到XElement?
E.g. myXElement.GetXPath()?
编辑: 好吧,看起来我忽略了一件非常重要的事情。哎呦!元素的索引需要考虑。请参阅我提供的最后一个答案,以解决提议的更正方案
我在文档中深入了解了XElement。鉴于XElement(和XDocument?),是否有扩展方法来获取其全部(即绝对,例如)XPath?获取XPath到XElement?
E.g. myXElement.GetXPath()?
编辑: 好吧,看起来我忽略了一件非常重要的事情。哎呦!元素的索引需要考虑。请参阅我提供的最后一个答案,以解决提议的更正方案
的扩展方法:
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.");
}
}
而且测试:
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]
这应该解决这个。没有?
如果您正在寻找.NET本地提供的东西,答案是否定的。你将不得不编写自己的扩展方法来做到这一点。
可能有几个xpath导致相同的元素,因此找到通向节点的最简单的xpath并不是微不足道的。
也就是说,找到节点的xpath非常简单。只需加紧节点树,直到读取根节点并合并节点名称并且有一个有效的xpath。
通过“full xpath”我假设你的意思是一个简单的标签链,因为可能匹配任何元素的xpaths的数量可能是非常大的。
这里的问题是,如果不是特别不可能建立任何给定的xpath,它将可逆地追溯到相同的元素是非常困难的 - 是一个条件?
如果“否”,那么也许您可以通过递归循环引用当前元素parentNode来构建查询。如果“是”,那么你会考虑通过交叉引用索引位置在同级集内进行扩展,如果它们存在,则引用ID类属性,如果通用解决方案将非常依赖于XSD是可能的。
这实际上是this问题的副本。尽管它没有被标记为答案,但是对于该问题的my answer中的方法是将XMLath明确地表达为XML文档中的节点的唯一方式,该XML文档将始终在所有情况下工作。 (它也适用于所有节点类型,而不仅仅是元素。)
正如你所看到的,它产生的XPath是丑陋和抽象的。但它解决了许多回答者在此提出的担忧。这里提出的大部分建议都会生成一个XPath,用于搜索原始文档时,将生成一组包含目标节点的一个或多个节点。这就是“甚至更多”这就是问题所在。例如,如果我有一个DataSet的XML表示,那么对特定DataRow元素/DataSet1/DataTable1
的朴素XPath还会返回DataTable中所有其他DataRow的元素。如果不知道XML是如何进行论坛化的(例如,是否存在主键元素?),您就不能消除歧义。
但是/node()[1]/node()[4]/node()[11]
,无论如何,只有一个它会返回的节点。
其实并不是严格意义上的重复。这个问题是关于`XDocument`和`XElement`(LINQ to XML),而引用的问题是关于`XmlNode`(System.Xml)。尽管如此,被引用问题中提出的方法很简单,可能很容易适应与LINQ to XML一起工作。 – SteveWilkinson 2011-06-20 14:54:01
所有缺少的是名称空间和属性,尽管这应该是微不足道的以适应代码提供它们。谢谢,罗伯特非常整洁 – Newtopian 2015-02-12 15:55:13
我更新了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.");
}
}
请注意,如果您的namespacePrefix计算结果为空字符串,您将得到一个带有无用分号的“:elementName”。没什么大不了的,但我想我会提到的。 – 2016-07-01 09:03:28
作为different project的一部分我开发了一个扩展方法来生成一个简单的XPath给一个元素。它与所选答案类似,但除XElement外,还支持XAttribute,XText,XCData和XComment。 它可在code nuget,项目页面在这里:xmlspecificationcompare.codeplex.com
让我分享我最新的修改到这个类。 Basicaly它排除索引如果元素没有兄弟姐妹,并包含名称空间与本地名()运算符我有问题的命名空间前缀。
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.");
}
}
微软已经提供了一个扩展的方法来做到这一点,因为.NET Framework 3.5的:
http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx
只是使用添加到System.Xml.XPath
并调用下面的方法:
XPathSelectElement
:选择单个元素XPathSelectElements
:选择元素,并返回为IEnumerable<XElement>
XPathEvaluate
:选择节点(不仅是要素,而且文本,注释等),并返回作为IEnumerable<object>
这对于没有名称空间的XML非常适用。对于具有名称空间的文档,除非您愿意忍受手动构建和传递XmlNamespaceManager的繁琐工作,否则[Chaveiro的答案](http://stackoverflow.com/a/23541182/3051203)就是要走的路。 – DumpsterDoofus 2015-12-28 21:22:39