题
我在文档深处有一个 XElement。给定 XElement (和 XDocument?),是否有扩展方法来获取其完整内容(即绝对的,例如 /root/item/element/child
) X 路径?
例如。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]
这应该解决这个。没有?
其他提示
我更新由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.");
}
}
让我分享我的最新修改此类。 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.");
}
}
这实际上是此的副本题。虽然它没有标记为答案,在我回答这个问题是明确制定的XPath的节点的XML文档,将永远在所有情况下的工作范围内的唯一途径。 (它也适用于所有的节点类型,而不仅仅是元素。)
可以看到,它产生的XPath是难看和抽象。但它解决了许多应答者在这里提出的问题。这里的大多数提出的建议产生,用于搜索原始文档时,会产生一组包括目标节点的一个或多个节点的XPath。这是说,“以上”这就是问题所在。举例来说,如果我有一个数据集的XML表示,天真的XPath特定的DataRow的元素,/DataSet1/DataTable1
,也将返回在DataTable中的其他数据行的元素。你不能说歧义不知道一些关于XML是如何forumlated(比如,有一个主键元素?)。
但/node()[1]/node()[4]/node()[11]
,还有就是它会不会回来,无论是只有一个节点是什么。
作为href="http://formaldev.blogspot.com.au/2013_08_01_archive.html" rel="nofollow">不同项目我开发了一个扩展方法来生成一个简单的XPath一个代码的NuGet ,项目页面在这里:的 xmlspecificationcompare.codeplex.com
如果你正在寻找由.NET本身提供的东西的答案是否定的。你将不得不编写自己的扩展方法来做到这一点。
可以有多个的XPath,导致相同的元件,因此能够发现通向节点的最简单的xpath并不是微不足道的。
这就是说,它是很容易找到的XPath到该节点。只要加强节点树,直到你读根节点,结合节点名称和你有一个有效的XPath。
这是“完全的XPath”我想你指的标签一个简单链,因为这有可能匹配任何元素可能的非常的大的XPath的数量。
这里的问题是,它很难,如果没有专门不可能建立任何给定的XPath将可逆追溯相同的元素 - ?是条件
如果“否”,则也许你可以通过参考当前元素parentNode递归循环构建查询。如果“是”,那么你将要寻找的延伸,通过交叉引用的兄弟姐妹集内的索引位置,如果存在的话referecing ID样的属性,这将是非常依赖于你的XSD如果一个通用的解决方案是可能的。
从 .NET Framework 3.5 开始,Microsoft 提供了一个扩展方法来执行此操作:
http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx
只需添加一个 using 即可 System.Xml.XPath
并调用以下方法:
XPathSelectElement
: :选择单个元素XPathSelectElements
: :选择元素并返回IEnumerable<XElement>
XPathEvaluate
: :选择节点(不仅是元素,还包括文本、注释等)并作为IEnumerable<object>