2017-09-01 35 views
1

我正在尋找一種方法來查找使用linq的XML樹中的第一個結果級別。該XML我已經是這樣的:如何找到給定名稱的最高級別後代

<column> 
    <row> 
     <object/> 
     <column column-id="1" column-name="abc"> 
      <row> 
       <column> 
        <row> 
         <column column-id="2" column-name="abc"/> 
        </row> 
       </column> 
      </row> 
     </column> 
    </row> 
    <row> 
     <column column-id="3" column-name="abc"> 
      <row> 
       <column/> 
      </row> 
     </column> 
    </row> 
</column> 

現在,我想所有的第一級columns其中列名是abc。所以結果應該是:

<column column-id="1" column-name="abc">...</column> 
<column column-id="3" column-name="abc">...</column> 

我已經嘗試下面的代碼:

layout.Descendants("column") 
     .Where(x => x.Attribute("column-name").Value.Equals("abc") && !x.Ancestors("column").Any()); 

當被搜索的XElement layout未命名"column"而不是嵌套命名"column"任何容器元素中也能正常工作。但我的XElement實際上屬於一個文檔,其根元素名爲"column",所以x.Ancestors("column").Any()表達式錯誤地過濾了所有匹配。即

var layout = XElement.Parse(xmlString); 

我想保持關係的變量,因爲改變我必須讓後來的:這個問題可以用XML字符串通過初始化layout如下轉載上述。

有沒有可能限制祖先選擇器的方法?

+1

請提供您的解決方案不起作用的示例。這將有所幫助。 –

+0

@GeorgeAlexandria - 實際上問題中的XML確實重現了這個問題,但從文本中看不清楚。 – dbc

回答

1

假設您事先不知道要查詢的元素的確切深度,您要做的是降低指定元素下的元素層次結構,並返回匹配的元素的最上面的元素給定的條件,在這種情況下名稱爲"column"

作爲一個快速和骯髒的方式做到這一點,你只能使用TakeWhile()

var matches = layout 
    .Descendants("column") 
    .Where(x => (string)x.Attribute("column-name") == "abc" && !x.Ancestors().TakeWhile(a => a != layout).Any(a => a.Name == "column")); 

更高性能,通用的解決方案會檢查是否存在仍是layout後裔候選匹配列的祖先在XElement上引入一個擴展方法,該方法枚舉給定元素的所有後代,返回匹配給定謂詞的最頂層元素。這通常是有用的,例如,在一個需要查詢爲將要接近了深刻的XML層次結構的頂部後裔,因爲它避免了不必要下降到匹配的節點情況:

public static partial class XElementExtensions 
{ 
    /// <summary> 
    /// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate 
    /// </summary> 
    /// <param name="root"></param> 
    /// <param name="filter"></param> 
    /// <returns></returns> 
    public static IEnumerable<XElement> DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf = false) 
    { 
     if (predicate == null) 
      throw new ArgumentNullException(); 
     return GetDescendantsUntil(root, predicate, includeSelf); 
    } 

    static IEnumerable<XElement> GetDescendantsUntil(XElement root, Func<XElement, bool> predicate, bool includeSelf) 
    { 
     if (root == null) 
      yield break; 
     if (includeSelf && predicate(root)) 
     { 
      yield return root; 
      yield break; 
     } 
     var current = root.FirstChild<XElement>(); 
     while (current != null) 
     { 
      var isMatch = predicate(current); 
      if (isMatch) 
       yield return current; 

      // If not a match, get the first child of the current element. 
      XElement next = (isMatch ? null : current.FirstChild<XElement>()); 

      if (next == null) 
       // If no first child, get the next sibling of the current element. 
       next = current.NextSibling<XElement>(); 

      // If no more siblings, crawl up the list of parents until hitting the root, getting the next sibling of the lowest parent that has more siblings. 
      if (next == null) 
      { 
       for (var parent = current.Parent as XElement; parent != null && parent != root && next == null; parent = parent.Parent as XElement) 
       { 
        next = parent.NextSibling<XElement>(); 
       } 
      } 

      current = next; 
     } 
    } 

    public static TNode FirstChild<TNode>(this XNode node) where TNode : XNode 
    { 
     var container = node as XContainer; 
     if (container == null) 
      return null; 
     return container.FirstNode.NextSibling<TNode>(true); 
    } 

    public static TNode NextSibling<TNode>(this XNode node) where TNode : XNode 
    { 
     return node.NextSibling<TNode>(false); 
    } 

    public static TNode NextSibling<TNode>(this XNode node, bool includeSelf) where TNode : XNode 
    { 
     if (node == null) 
      return null; 
     for (node = (includeSelf ? node : node.NextNode); node != null; node = node.NextNode) 
     { 
      var nextTNode = node as TNode; 
      if (nextTNode != null) 
       return nextTNode; 
     } 
     return null; 
    } 
} 

然後使用它像:

var matches = layout 
    .DescendantsUntil(x => x.Name == "column") 
    .Where(x => (string)x.Attribute("column-name") == "abc"); 

擴展方法應該是合理的高性能,因爲它避免了遞歸和複雜的嵌套linq查詢。

樣本.Net fiddle顯示兩個選項。

+1

非常感謝這個解決方案。 「DescendantsUntil」的使用確實解決了我的問題。 –

0

陳述問題的另一種方式是,您希望謂詞包含距文檔根目錄的距離。

這裏是做一個函數:

static int DistanceToRoot(XElement elem, XElement root) 
{ 
    var dist = 0; 

    var curr = elem; 

    while(curr != root) 
    { 
     dist++; 
     curr = curr.Parent; 
    } 

    return dist; 
} 

並且你使用像這樣(根據你的榜樣,我們要的距離):

var columns = from column in xml.Descendants("column") 
       where 
        DistanceToRoot(column, xml.Root) == 2 && 
        column.Attribute("column-name").Value == "abc" 
       select column; 


foreach(var abc in xyzs) 
{ 
    Console.WriteLine(abc); 
    Console.Write("Distance is: "); 
    Console.WriteLine(DistanceToRoot(abc, xml.Root)); 
    Console.ReadLine(); 
} 

哪結果在:

<column column-id="1" column-name="abc"> 
    <row> 
    <column> 
     <row> 
     <column column-id="2" column-name="abc" /> 
     </row> 
    </column> 
    </row> 
</column> 
Distance is: 2 

<column column-id="3" column-name="abc"> 
    <row> 
    <column /> 
    </row> 
</column> 
Distance is: 2 

Rextester Demo

相關問題