2009-01-16 75 views
39

我在文檔中深入瞭解了XElement。鑑於XElement(和XDocument?),是否有擴展方法來獲取其全部(即絕對,例如)XPath?獲取XPath到XElement?

E.g. myXElement.GetXPath()?

編輯: 好吧,看起來我忽略了一件非常重要的事情。哎呦!元素的索引需要考慮。請參閱我提供的最後一個答案,以解決提議的更正方案

回答

36

的擴展方法:

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] 

這應該解決這個。沒有?

+0

這對於沒有名稱空間的XML非常適用。對於具有名稱空間的文檔,除非您願意忍受手動構建和傳遞XmlNamespaceManager的繁瑣工作,否則[Chaveiro的答案](http://stackoverflow.com/a/23541182/3051203)就是要走的路。 – DumpsterDoofus 2015-12-28 21:22:39

0

如果您正在尋找.NET本地提供的東西,答案是否定的。你將不得不編寫自己的擴展方法來做到這一點。

0

可能有幾個xpath導致相同的元素,因此找到通向節點的最簡單的xpath並不是微不足道的。

也就是說,找到節點的xpath非常簡單。只需加緊節點樹,直到讀取根節點併合並節點名稱並且有一個有效的xpath。

0

通過「full xpath」我假設你的意思是一個簡單的標籤鏈,因爲可能匹配任何元素的xpaths的數量可能是非常大的

這裏的問題是,如果不是特別不可能建立任何給定的xpath,它將可逆地追溯到相同的元素是非常困難的 - 是一個條件?

如果「否」,那麼也許您可以通過遞歸循環引用當前元素parentNode來構建查詢。如果「是」,那麼你會考慮通過交叉引用索引位置在同級集內進行擴展,如果它們存在,則引用ID類屬性,如果通用解決方案將非常依賴於XSD是可能的。

4

這實際上是this問題的副本。儘管它沒有被標記爲答案,但是對於該問題的my answer中的方法是將XMLath明確地表達爲XML文檔中的節點的唯一方式,該XML文檔將始終在所有情況下工作。 (它也適用於所有節點類型,而不僅僅是元素。)

正如你所看到的,它產生的XPath是醜陋和抽象的。但它解決了許多回答者在此提出的擔憂。這裏提出的大部分建議都會生成一個XPath,用於搜索原始文檔時,將生成一組包含目標節點的一個或多個節點。這就是「甚至更多」這就是問題所在。例如,如果我有一個DataSet的XML表示,那麼對特定DataRow元素/DataSet1/DataTable1的樸素XPath還會返回DataTable中所有其他DataRow的元素。如果不知道XML是如何進行論壇化的(例如,是否存在主鍵元素?),您就不能消除歧義。

但是/node()[1]/node()[4]/node()[11],無論如何,只有一個它會返回的節點。

+1

其實並不是嚴格意義上的重複。這個問題是關於`XDocument`和`XElement`(LINQ to XML),而引用的問題是關於`XmlNode`(System.Xml)。儘管如此,被引用問題中提出的方法很簡單,可能很容易適應與LINQ to XML一起工作。 – SteveWilkinson 2011-06-20 14:54:01

+0

所有缺少的是名稱空間和屬性,儘管這應該是微不足道的以適應代碼提供它們。謝謝,羅伯特非常整潔 – Newtopian 2015-02-12 15:55:13

10

我更新了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."); 
    } 
} 
+2

請注意,如果您的namespacePrefix計算結果爲空字符串,您將得到一個帶有無用分號的「:elementName」。沒什麼大不了的,但我想我會提到的。 – 2016-07-01 09:03:28

4

讓我分享我最新的修改到這個類。 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."); 
    } 
} 
-1

微軟已經提供了一個擴展的方法來做到這一點,因爲.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>