LINQ之路18:LINQ to XML之導航和查詢
正如我們期望的那樣,XNode和XContainer類定義了用于遍歷X-DOM tree的方法和屬性。但是和傳統的DOM不同,這些方法并不返回IList<T>集合,而是返回單個值或者實現了IEnumerable<T>的sequence(這樣我們就可以對其創建LINQ查詢了)。本篇我們會講述X-DOM的各種導航方法。
子節點導航/Child Node Navigation
|
返回類型 |
成員 |
目標對象 |
|
XNode |
FirstNode { get; } |
XContainer |
|
LastNode { get; } |
XContainer |
|
|
IEnumerable<XNode> |
Nodes() |
XContainer* |
|
DescendantNodes() |
XContainer* |
|
|
DescendantNodesAndSelf() |
XElement* |
|
|
XElement |
Element (XName) |
XContainer |
|
IEnumerable<XElement> |
Elements() |
XContainer* |
|
Elements (XName) |
XContainer* |
|
|
Descendants() |
XContainer* |
|
|
Descendants (XName) |
XContainer* |
|
|
DescendantsAndSelf() |
XElement* |
|
|
DescendantsAndSelf (XName) |
XElement* |
|
|
bool |
HasElements { get; } |
XElement |
在目標對象列標記了*的函數同樣可以應用于該目標對象sequence(本系列LINQ to XML博客中其他表格也一樣)。例如,我們可以對一個XContainer或XContainer sequence調用Nodes()函數。作用于sequence的函數會把針對其中每個元素所得的結果連接起來。之所以可以這樣工作,是因為定義在System.Xml.Linq中的擴展方法,即前面提到的補充查詢運算符。
FirstNode, LastNode, 和Nodes
FirstNode和LastNode可以讓我們直接訪問第一個和最后一個子節點;Nodes返回所有的子節點到一個sequence中。所有三個方法都只返回直接的后代節點。
示例如下:
var bench = new XElement("bench",
new XElement("toolbox",
new XElement("handtool", "Hammer"),
new XElement("handtool", "Rasp")
),
new XElement("toolbox",
new XElement("handtool", "Saw"),
new XElement("powertool", "Nailgun")
),
new XComment("Be careful with the nailgun")
);
foreach (XNode node in bench.Nodes())
Console.WriteLine(node.ToString(SaveOptions.DisableFormatting));
//輸出如下:
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>
<!--Be careful with the nailgun-->
檢索元素
Elements方法僅返回XElement類型的子節點:
foreach (XElement e in bench.Elements())
Console.WriteLine(e.Name + "=" + e.Value);
// 輸出:
toolbox=HammerRasp
toolbox=SawNailgun
下面的LINQ查詢查找包含”Nailgun”的toolbox:
IEnumerable<string> query =
from toolbox in bench.Elements()
where toolbox.Elements().Any (tool => tool.Value == "Nailgun")
select toolbox.Value;
RESULT: { "SawNailgun" }
下面的示例使用一個SelectMany查詢來檢索hand tools:
IEnumerable<string> query =
from toolbox in bench.Elements()
from tool in toolbox.Elements()
where tool.Name == "handtool"
select tool.Value;
RESULT: { "Hammer", "Rasp", "Saw" }
Elements方法等價于在Nodes上的一個LINQ查詢,比如前一個示例中的Elements方法也可以用如下查詢實現:
from toolbox in bench.Nodes().OfType<XElement>()
where ...
Elements也可以只返回給定名字的elements,如:
int x = bench.Elements ("toolbox").Count(); // 2
// 等價于
int x = bench.Elements().Where (e => e.Name == "toolbox").Count(); // 2
IEnumerable<XContainer>也定義了名為Elements的擴展方法,這讓我們可以對一個element sequence調用Elements方法。所以,上面檢索hand tools的SelectMany查詢可以重寫為如下形式:
IEnumerable<string> query =
from tool in bench.Elements("toolbox").Elements("handtool")
select tool.Value;
第一個Elements方法綁定到XContainer的實例方法,而第二個則會調用IEnumerable<XContainer>中的擴展方法。
檢索單個元素
單數形式的Element方法返回匹配給定名稱的第一個元素。Element對于單個元素的導航非常有用,示例如下:
XElement settings = XElement.Load("databaseSettings.xml");
string cs = settings.Element("database").Element("connectString").Value;
Element等價于在Elements()之后應用LINQ的FirstOrDefault查詢運算符(指定一個name匹配條件)。如果查詢的元素不存在,Element返回null。
如果xyz元素不存在,Element("xyz").Value會拋出空引用異常。如果我們希望得到一個null值而不是異常,則可以把XElement轉為一個string,而不是查詢它的Value屬性:
string xyz = (string)settings.Element("xyz");
這種方法之所以可行,是因為XElement定義了顯示的到string類型的轉換,為的就是這個目的。如果element存在,(string)element返回其Value屬性,否則,返回null。
遞歸函數
XContainer還提供了Descendants和DescendantNodes方法,用來遞歸地返回child elements或nodes,即所有的后代elements或nodes。Descendants可選的接受一個element名稱。
回到我們前面的例子,我們可以使用Descendants來找到所有的hand tools:
Console.WriteLine(bench.Descendants("handtool").Count()); // 3
調用Descendants方法時,符合條件的parent和leaf nodes都會被包含進來, 如下所示:
foreach (XNode node in bench.DescendantNodes())
Console.WriteLine(node.ToString(SaveOptions.DisableFormatting));
// 結果如下:
<toolbox><handtool>Hammer</handtool><handtool>Rasp</handtool></toolbox>
<handtool>Hammer</handtool>
Hammer
<handtool>Rasp</handtool>
Rasp
<toolbox><handtool>Saw</handtool><powertool>Nailgun</powertool></toolbox>
<handtool>Saw</handtool>
Saw
<powertool>Nailgun</powertool>
Nailgun
<!--Be careful with the nailgun-->
下面的查詢會檢索出所有包含”careful”的XML注釋,而不管該注釋位于X-DOM之內的任何位置:
IEnumerable<string> query =
from c in bench.DescendantNodes().OfType<XComment>()
where c.Value.Contains("careful")
orderby c.Value
select c.Value;
父節點導航/Parent Navigation
所有的XNodes都為父節點導航提供了Parent屬性和AncestorXXX方法。一個Parent/父節點總是一個XElement:
|
返回類型 |
成員 |
目標對象 |
|
XElement |
Parent { get; } |
XNode* |
|
Enumerable<XElement> |
Ancestors() |
XNode* |
|
Ancestors (XName) |
XNode* |
|
|
AncestorsAndSelf() |
XElement* |
|
|
AncestorsAndSelf (XName) |
XElement* |
如果x是一個XElement,下面的語句總是輸出true:
foreach (XNode child in x.Nodes())
Console.WriteLine(child.Parent == x);
但如果x是一個XDocument,行為則有所不同。XDocument的特殊性在于:它可以有children,但它永遠都不會成為其他node的parent!要存取XDocument,我們必須使用Document屬性,它對X-DOM tree中的任意對象都有效。
Ancestors返回一個sequence,它的第一個element是Parent節點,下一個節點是Parent.Parent,依次類推,直到根節點。
我們可以通過如下的LINQ查詢來得到根節點:AncestorsAndSelf().Last()。
另一種得到根節點的方法是調用Document.Root,當然這種方法只能在XDocument對象存在的情況下使用。
兄弟節點導航/Peer Node Navigation
|
返回類型 |
成員 |
目標對象 |
|
bool |
IsBefore (XNode node) |
XNode |
|
IsAfter (XNode node) |
XNode |
|
|
XNode |
PreviousNode { get; } |
XNode |
|
NextNode { get; } |
XNode |
|
|
IEnumerable<XNode> |
NodesBeforeSelf() |
XNode |
|
|
NodesAfterSelf() |
XNode |
|
IEnumerable<XElement> |
ElementsBeforeSelf() |
XNode |
|
ElementsBeforeSelf (XName name) |
XNode |
|
|
ElementsAfterSelf() |
XNode |
|
|
ElementsAfterSelf (XName name) |
XNode |
對于PreviousNode和NextNode(還有FirstNode/LastNode),我們可以像一個鏈表/linked list那樣來遍歷節點。實際上,nodes再內部實現時就是存放在鏈表中。XNode在內部使用的是單向鏈表,所以調用PreviousNode的性能不會很好。
XElement e = new XElement("customers",
new XElement("customer", "Tom"),
new XElement("customer", "Dick"),
new XElement("customer", "Harry"),
new XElement("customer", "Mary"),
new XElement("customer", "Jay"));
Console.WriteLine(e.ToString());
XElement current = e.Elements().Single(x => x.Value == "Harry");
bool flag1 = current.IsBefore(e.FirstNode); // false
bool flag2 = current.IsAfter(e.FirstNode); // true
XNode pNode = current.PreviousNode; // <customer>Dick</customer>
XNode nNode = current.NextNode; // <customer>Mary</customer>
var query = current.NodesBeforeSelf(); // <customer>Tom</customer> & <customer>Dick</customer>
對于其他方法就不再詳細討論了,方法名稱就說明了一切。記得Node和Element的區別就行!
屬性導航/Attribute Navigation
|
返回類型 |
成員 |
目標對象 |
|
bool |
HasAttributes { get; } |
XElement |
|
XAttribute |
Attribute (XName name) |
XElement |
|
FirstAttribute { get; } |
XElement |
|
|
LastAttribute { get; } |
XElement |
|
|
IEnumerable<XAttribute> |
Attributes() |
XElement |
|
Attributes (XName name) |
XElement |
此外,XAttribute還定義了PreviousAttribute、NextAttribute和Parent屬性。
帶有一個name參數的Attributes方法返回一個sequence,其中會包含零個或一個元素,因為一個XML中的XElement不能擁有相同名字的attributes。
下一篇會和大家討論X-DOM的更新技術

浙公網安備 33010602011771號