LINQ之路17:LINQ to XML之X-DOM介紹
.NET Framework提供了數種操作XML數據的API。從Framework 3.5開始,最重要的用來處理XML文檔的技術當屬LINQ to XML。LINQ to XML由一個輕量級的XML文檔對象模型和一組補充查詢運算符組成,并且,該文檔對象模型是LINQ友好的。多數情況下,它可以完全取代XML技術的前身:符合W3C規則的DOM,如XmlDocument。現在,就讓我們一起開始LINQ to XML的學習之旅,看看它是怎樣簡化XML的查詢與操作,提高我們的工作效率的。
所有的LINQ to XML類型都定義在System.Xml.Linq命名空間中,請記住在運行相關示例時導入該命名空間。
架構預覽
認識DOM
讓我們先來簡單認識一下DOM的概念,然后再來解釋LINQ to XML’s DOM背后的基本原理。
考慮下面的XML文件:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<customer id="123" status="archived">
<firstname>Joe</firstname>
<lastname>Bloggs</lastname>
</customer>
和所有的XML文件一樣,我們從一個XML聲明開始,然后是root元素,元素名為customer。它有2個attributes,名字分別為id和status,值分別為”123”和”archived”。在customer元素之內,包含了兩個子元素firstname和lastname,分別包含一個文本內容 ("Joe"和"Bloggs")。
上面的每一種結構:declaration, element, attribute, value, 和text content,都可以用一個相應的類來表示。如果為這些類加上某些集合屬性來存儲子內容,那么我們就可以通過一個對象樹來完整的描述一個文檔,這就是文檔對象模型,簡稱DOM。
LINQ to XML文檔對象模型(DOM)
LINQ to XML由下面兩部分內容組成:
- 一個XML DOM,我們可以稱為X-DOM
- 一組(大約10個)補充查詢運算符
X-DOM包含的類型有XDocument、XElement和XAttribute等。有意思的是,X-DOM并不是和LINQ綁定在一起的,我們可以撇開LINQ查詢而單獨裝載、實例化、更新和保存X-DOM類型。相應的,我們可以使用LINQ來查詢老的W3C式樣的XML類型,當然,這會有很多限制。
X-DOM的重要特征是它是LINQ友好的,這意味著:
- 它提供了產生IEnumerable sequences的方法,然后我們就可以在sequence之上來建立查詢。
- 它的構造函數允許我們通過一個LINQ數據轉換來創建一個X-DOM樹
X-DOM介紹
下圖顯示了X-DOM的核心類型。使用最頻繁的類型當屬XElement。XObject是該繼承層次的根類型;XElement和XDocument是具體的容器類型。

XObject
XObject所有XML數據的抽象基類,它定義了Parent element和XDocument。
XNode
XNode是大部分XML數據的基類(除了XAttribute)。一個XNode可以位于一個融合了多種XNode類型的有序集合之中,比如下面的XML:
<data>
Hello world
<subelement1/>
<!--comment-->
<subelement2/>
</data>
在父元素<data>之內,首先是一個XText node (Hello world),然后是一個XElement node,然后是XComment node,最后是第二個XElement node。
盡管一個XNode可以訪問它的父元素,但是它并沒有子節點/child nodes的概念。子節點由XContainer類提供。XNode除了包括XElement,它還包含了XText、XComment等類型節點(見上圖),在后面講述X-DOM導航時你會看到各種XXXNodes和XXXElements方法,請記得這個區別。
XContainer
XContainer定義了處理子節點的方法,它是XElement和XDocument的抽象基類。
XElement
XElement包含了管理屬性的方法,以及Name和Value成員。并且,由于它的基類是XContainer,所以它可以包含子節點。通常情況下,一個element會有單個XText子節點,并且Value屬性封裝了對該XText子節點的操作。所以,多數情況下,我們并不需要直接和XText打交道。
XDocument
XDocument表示了XML樹的根節點。它包裝了root XElement、一個XDeclaration、processing instructions、和其他根級類型對象。和W3C DOM不同的是,對XDocument的使用是可選的,我們可以在不創建XDocument的情況下裝載、操作和保存一個X-DOM!對XDocument的獨立性甚至意味著我們可以高效的把一個node子樹移動到另一個不相關的X-DOM層次對象中去。
Loading和Parsing
Xelement和XDocument都提供了靜態的Load和Parse方法,他們用來創建X-DOM tree。
- Load用來從file、URI、Stream、TextReader或XmlReader創建X-DOM
- Parse用來從string創建X-DOM
示例如下:
XDocument fromWeb = XDocument.Load("http://albahari.com/sample.xml");
XElement fromFile = XElement.Load(@"e:\media\somefile.xml");
XElement config = XElement.Parse(
@"<configuration>
<client enabled='true'>
<timeout>30</timeout>
</client>
</configuration>");
保存和序列化
調用任意node的ToString()方法都會把它的內容轉換到一個XML string,該string由分行符和縮進格式化,當然我們可以通過SaveOptions.DisableFormatting屬性來取消分行符合縮進。
XElement和XDocument還提供了Save方法來把一個X-DOM寫到一個file, Stream, TextWriter, 或 XmlWriter。如果指定的是一個file,則會自動添加一個XML declaration。對XML declarations的詳細介紹會在之后給出。
實例化X-DOM
除了使用Load和Parse方法,我們還可以通過實例化對象并把他們添加至父節點來創建X-DOM tree。
// 構建XElement和XAttribute時,只需提供name和value
XElement lastName = new XElement("lastname", "Bloggs");
lastName.Add(new XComment("nice name"));
XElement customer = new XElement("customer");
customer.Add(new XAttribute("id", 123));
customer.Add(new XElement("firstname", "Joe"));
customer.Add(lastName);
Console.WriteLine(customer.ToString());
結果如下:
<customer id="123">
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</customer>
創建XElement時,value參數是可選的,我們可以只指定name而在此之后再添加內容。當我們提供了value屬性時,一個簡單的string就可以了,XDOM會自動將其轉換為XText子節點。
函數式構造/Functional Construction
在上一個例子中,我們很難從代碼中看出XML的結構。現在,XDOM支持了另外一種實例化模式:函數式構造(由函數式編程而來)。通過函數式構造,我們在一個表達式中就可以創建完整的XDOM tree:
XElement customer =
new XElement("customer", new XAttribute("id", 123),
new XElement("firstname", "joe"),
new XElement("lastname", "bloggs",
new XComment("nice name")
)
);
這樣有兩個優勢。首先,代碼反映了XML的結構;其次,它可以與LINQ查詢的select子句進行交互。比如,下面的LINQ to SQL查詢直接把結果轉換到一個X-DOM:
XElement query =
new XElement("customers",
from c in dataContext.Customers
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("firstname", c.FirstName),
new XElement("lastname", c.LastName,
new XComment("nice name")
)
)
);
這種技術的詳細信息會在后續篇章中再作介紹。
函數式構造工作方式
函數式構造之所以可行,是因為XElement和XDocument的構造函數提供了如下的重載形式:
// params object[]意味著可以接受任意數量的參數
public XElement (XName name, params object[] content)
// XContainer的Add方法也是如此:
public void Add (params object[] content)
所以,當我們創建或添加X-DOM時可以指定任意數量任意類型的子對象(child objects)。那么XContainer為何能接受任意類型的子對象呢?要知道原因,我們就需要來查看它們在內部實現中是如何處理每個子對象的。下面就是XContainer類型(XElement和XDocument的基類型)對于子對象的處理方式:
- 如果該對象為空,忽略它
- 如果該對象基于XNode或XStreamingElement,添加至Nodes集合。
- 如果該對象是一個XAttribute,添加至Attributes集合。
- 如果是一個string,用XText節點封裝該string并添加到Nodes集合。
- 如果該對象實現了IEnumerable,則遍歷它,上面的規則會應用到其中每一個element。
- 否則,該對象會被轉換至一個string,經XText節點封裝后添加到Nodes集合。
所有的數據最后都會被添加到Nodes或Attributes集合之一,并且,任何對象都是有效的,因為不管怎樣,我們都可以調用ToString來把它轉換成一個string。
那么上面LINQ查詢中的select語句是如何向XElement中添加元素的呢,記住,LINQ查詢結果為一IEnumerable<T>(上例T為XElement) sequence,所以應用規則5,遍歷該sequence,把每一個XElement都添加至name為customers的XElement之中。
自動完全克隆
當一個node或attribute被添加到某個element后,它的Parent屬性就指向了該element。一個node只能有一個parent element。所以當你把一個已經有Parent屬性的node添加到另一個element后,該node會被自動深克隆(deep-cloned)。示例如下:
// 每個customer都有address對象的一份拷貝
var address = new XElement("address",
new XElement("street", "Hong mei"),
new XElement("town", "Xu hui")
);
var customer1 = new XElement("customer1", address);
var customer2 = new XElement("customer2", address); // address被同時添加至customer1和customer2
customer1.Element("address").Element("street").Value = "Yi shan"; //修改customer1的address
Console.WriteLine(customer2.Element("address").Element("street").Value); // Hong mei, customer2的address并沒有被修改
這種自動復制的技術保證了X-DOM對象實例化時避免了意想不到的副作用。
在下一篇中,我們將會對LINQ to XML的導航查詢功能進行詳細的介紹。

浙公網安備 33010602011771號