LINQ to XML深入剖析
LINQ to XML提供了為豐富并且簡潔的類來實現(xiàn)對XML的操作。相對于種類繁多的DOM模型的XML類庫而言,LINQ的類使我們的學(xué)習(xí)曲線變得平滑并且還能達到相同的效果。LINQ to XML解決了DOM模型中的幾個比較不方便的問題,如修改節(jié)點名字的問題;同時也拋棄了一些看起來很強大但是很不常用的東西,如實體和實體引用。這樣使得LINQ to XML的操作速度更快并且更方便。以下的幾個例子將展示給大家LINQ to XML如何完成節(jié)點名稱修改,增加和刪除的效果。
首先,我們看一下添加一個節(jié)點到XML中是這么樣實現(xiàn)的:
- XElement xelem = XElement.Load(@"example.xml");
- XElement newnewXelem = new XElement("NewNode", "This is new node");
- xelem.Add(newXelem);
相當(dāng)?shù)暮唵?,只要先生成一個XElement對象然后把它Add到當(dāng)前節(jié)點對象就可以了。進一步我們仔細查看一下XElement可以添加節(jié)點的方法??梢钥吹揭还灿蠥dd, AddAfterSelf, AddAnnotation, AddBeforeSelf, AddFirst這五個方法。在默認(rèn)情況下,Add的操作是將新節(jié)點作為被插入節(jié)點的最后一個孩子節(jié)點插入的,而AddFirst正好相反。AddAfterSelf和AddBeforeSelf則是將節(jié)點作為兄弟節(jié)點插入的,這里要注意的是調(diào)用這兩個方法的時候不能以根節(jié)點作為被插入節(jié)點,因為XML文檔規(guī)定只能有一個根節(jié)點。最后,我們來看一下AddAnnotation這個方法。
AddAnnotation是為一個節(jié)點添加一個相關(guān)的評注的類對象。這個類對象可以用戶自己定義,所以通過這個方法我們可以擴展XML文檔對象的功能,例如根據(jù)節(jié)點來獲取類對象的功能。下面是一段引用自msdn的代碼:
- public class MyAnnotation
- {
- private string tag;
- public string Tag { get { return tag; } set { tag = value; } }
- public MyAnnotation(string tag)
- {
- this.tag = tag;
- }
- }
- … …
- MyAnnotation ma = new MyAnnotation("T1");
- XElement root = new XElement("Root", "content");
- root.AddAnnotation(ma);
- MyAnnotation ma2 = (MyAnnotation)root.Annotation<MyAnnotation>();
接下來我們再看一下如何利用LINQ to XML來更新XML的信息。對XML文檔進行更新主要包括兩個方面,一個方面是對元素屬性和值得更新;另一方面是對元素名稱的更新。在一般情況下,我們通常只對元素的屬性和值進行更新,代碼如下:
- XElement xelem = XElement.Load(@"example.xml");
- var partNos = from item in xelem.Descendants("Item")
- where item.Attribute("PartNumber").Value == "872-AA"
- select item;
- foreach (XElement node in partNos)
- {
- node.Value = "Hello";
- Console.WriteLine(node.Value);
- }
對于XML元素的更新操作,關(guān)鍵就是在于查詢上面,如何有效并準(zhǔn)確的查詢到目標(biāo)元素是一個比較有挑戰(zhàn)性的問題,這就像SQL語句一樣對于同一個查詢不同的寫法和關(guān)系連接效率就不一樣,這個讀者需要自己多加練習(xí)。在上面的代碼段中,我們?nèi)ゲ檎宜袑傩詾镻artNumber=872-AA的元素并將其更新為Hello。那么如果想操作元素值,只需將修改查詢條件為:
- var partNos = from item in xelem.Descendants("ProductName")
- where item.Value == "Lawnmower"
XML文檔的元素名稱更新相比較于值更新要麻煩許多。由于XML文檔結(jié)構(gòu)是一個類樹形結(jié)構(gòu),學(xué)過數(shù)據(jù)結(jié)構(gòu)的讀者知道要更新一個樹節(jié)點的指針,最少需要三個步驟 :
A. 查找目標(biāo)樹節(jié)點的父節(jié)點
B. 將先前的節(jié)點的孩子轉(zhuǎn)移到新節(jié)點
C. 將父節(jié)點的子節(jié)點替換(如果需要還要用算法重新排序樹)。
所以我們也需要用同樣的邏輯來處理XML節(jié)點的更新。那么相比較DOM文檔對象和LINQ to XML對元素名稱更新操作,LINQ to XML的步驟要簡單許多。
- XElement xel = XElement.Load(@"example.xml");
- var itemNos = from item in xel.Descendants("Item")
- select item;
- int n = itemNos.Count();
- for (int i=0; i<n; i++)
- {
- // 新創(chuàng)建節(jié)點
- XElement nEl = new XElement("Element");
- // 轉(zhuǎn)移孩子節(jié)點
- nEl.Add(itemNos.ElementAt(0).Elements());
- // 替換
- itemNos.ElementAt(0).ReplaceWith(nEl);
- }
- Console.Write(xel);
上面的代碼是將所有名稱為Item的元素替換成名稱為Element。細心的讀者可以看到我在for循環(huán)中獲取itemNos的孩子都使用0這個索引值,為什么呢?這是因為在枚舉器中如果前面的對象消失那么索引位置就會下移,那么當(dāng)我們替換一個元素后,下一個元素的索引自動變?yōu)?,所以我們只要循環(huán)指定次數(shù)就可以遍歷所有元素來。這也是為什么不用foreach的原因。那么我們進一步的出思考XML名稱替換這個問題。我們會發(fā)現(xiàn)LINQ to XML的 XElement類提供了4個方法用來支持該功能:ReplaceAll, ReplaceAttributes, ReplaceNodes和ReplaceWith。這四個方法除了ReplaceWith是操作本元素以為,其他的都是操作元素的孩子或是屬性內(nèi)容。這里提供的好處是如果我們想遍歷替換操作,就不必去重復(fù)的查詢目標(biāo)元素。
最后,我們討論一下如何利用LINQ to XML來刪除一個元素。對于類樹形結(jié)構(gòu)的數(shù)據(jù)來說,刪除一個元素意味著兩種情況:一、刪除本節(jié)點和其所有子節(jié)點;二、只刪除本節(jié)點。而在刪除節(jié)點之前,我們需要先定位到目標(biāo)節(jié)點,所以要先進行查詢操作:
- XElement xelem = XElement.Load(@"example.xml");
- var partNos = from item in xelem.Descendants("Item")
- where item.Attributes("PartNumber").Single().Value == "872-AA"
- select item;
- partNos.Remove();
- Console.Write(xelem);
【編輯推薦】