我在文檔中深入瞭解了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