C#學習之Linq to Xml


前言

我相信很多從事.NET開發的,在.NET 3.5之前操作XML會比較麻煩,但是在此之后出現了Linq to Xml,而今天的主人公就是Linq to Xml,廢話不多說,直接進入主題。

題外:最近由於身體原因去醫院,耽誤了不少時間,不然這篇隨筆可能早就完成了。

 

實例項目下載 

 

目錄: 

生成xml

創建簡單的xml

創建注釋

根據對象創建xml

創建屬性

創建命名空間

查詢並修改xml

通過文件讀取xml

在指定節點前后添加新節點

添加屬性到節點中

添加注釋到指定節點前后

替換指定節點

刪除指定屬性

刪除指定節點

 按節點關系查詢

顯示指定節點的所有父節點

顯示指定節點的所有子節點

顯示同級節點之前的節點

顯示同級節點之后的節點

監聽xml事件

處理xml流

 

一、生成Xml

為了能夠在結構有一定的組織,筆者建議大家新建一個控制台項目,並且新建一個CreateXml(以下部分都屬於該類中)

並在其中寫入以下屬性:

1         public static String Path
2         {
3             get
4             {
5                 String path = String.Format("{0}\\test.xml", Environment.CurrentDirectory);
6                 return path;
7             }
8         }

 

 

這句代碼很好理解,就是為了下面我們示例的時候可以將xml保存到當前程序的運行路徑下。

以下的示例中不會包含Main方法中的寫法,因為Main中僅僅只要調用該靜態方法即可。

 

1.創建簡單的Xml

首先我們先練練手,創建一個簡單的Xml並保存到一個文件中。

代碼如下:

 1 /// <summary>
 2 /// 創建簡單的xml並保存
 3 /// </summary>
 4 public static void CreateElement()
 5 {
 6 XDocument xdoc = new XDocument(
 7 new XDeclaration("1.0", "utf-8", "yes"),
 8 new XElement("root",
 9 new XElement("item", "1"),
10 new XElement("item", "2")
11 ));
12 xdoc.Save(Path);
13 }

 

 

很多學習過XML的人可以從結構就能夠猜測出最終的xml的組織,而這也是linq to xml的優點之一。這句代碼首先創建一個xml文檔,並設置該xml的版本為1.0,

采用utf-8編碼,后面的yes表示該xml是獨立的。下面就開始創建每個節點的,首先是Root節點,然后在Root節點中添加兩個Item節點。

 

最終生成的Xml如下所示:

1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2 <root>
3   <item>1</item>
4   <item>2</item>
5 </root>

 

 

2.創建注釋

xml有很多項時,我們就需要利用注釋加以區別,通過linq to xml我們一樣可以在其中添加注釋。

 

比如下面這段代碼:

 1         /// <summary>
 2         /// 創建注釋
 3         /// </summary>
 4         public static void CreateComment()
 5         {
 6             XDocument doc = new XDocument(
 7                 new XDeclaration("1.0", "utf-8", "yes"),
 8                 new XComment("提示"),
 9                 new XElement("item", "asd")
10                 );
11             doc.Save(Path);
12         }

 

 

這里我們直接在版本信息的后面添加了一條注釋。

 

最終的結果如下所示:

1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2 <!--提示-->
3 <item>asd</item>

 

 

3.根據對象創建xml

很多時候我們都會將數組之類的類型轉換成xml以便保存進永久性存儲介質中,所以下面我們也簡單的舉了一個例子,將數組轉換成xml

 

代碼如下所示:

 1         /// <summary>
 2         /// 根據對象創建xml並保存
 3         /// </summary>
 4         public static void CreateElementByObjects()
 5         {
 6             var s = Enumerable.Range(1, 10);
 7             XElement xele = new XElement(
 8                 "Root",
 9                 from item in s
10                 select new XElement("item", item.ToString())
11                 );
12             xele.Save(Path);
13         }

 

 

一開始的代碼 var s = Enumerable.Radge(1,10)是從1開始遞增,生成含有10項的數組,以便后面我們進行添加,有了這個數組之后,

我們通過簡單的linq語句將數組轉換成xml,添加到Root中。

 

保存之后的結果如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <Root>
 3   <item>1</item>
 4   <item>2</item>
 5   <item>3</item>
 6   <item>4</item>
 7   <item>5</item>
 8   <item>6</item>
 9   <item>7</item>
10   <item>8</item>
11   <item>9</item>
12   <item>10</item>
13 </Root>

 

 

4.創建屬性

有時我們不想創建新的子項去保存數據,而是使用屬性的方式去保存。理所應當,linq to xml一樣也支持這個功能,下面我們可以通過簡單的語句去實現它。

 

代碼如下所示:

 1         /// <summary>
 2         /// 創建屬性
 3         /// </summary>
 4         public static void CreteAttribute()
 5         {
 6             XAttribute xa = new XAttribute("V2", "2");
 7             XElement xele = new XElement(
 8                 "Root",
 9                 new XElement("Item",
10                     new XAttribute("V1", "1"),
11                     xa
12                     ));
13             xele.Save(Path);
14         }

 

 

我們依然可以看到熟悉的語法,這里我們利用了XAttribute去創建一個屬性,並添加到XElement中。

 

最終的結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <Item V1="1" V2="2" />
4 </Root>

 

 

5.創建命名空間

對於一些企業級的xml格式,會非常的嚴格。特別是在同一個xml中可能會出現重復的項,但是我們又想區分開來,這個時候我們可以利用命名空間將他們分開(跟C#中的命名空間類似。)。

 

下面是創建命名空間的示例:

 1         /// <summary>
 2         /// 創建命名空間
 3         /// </summary>
 4         public static void CreateNamespace()
 5         {
 6             XElement xele = new XElement("{http://www.xamarin-cn.com}Root",
 7                 new XElement("Item", "1"),
 8                 new XElement("{http://www.baidu.com}Item", 2));
 9             xele.Save(Path);
10         }

 

 

結果如下所示:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root xmlns="http://www.xamarin-cn.com">
3   <Item xmlns="">1</Item>
4   <Item xmlns="http://www.baidu.com">2</Item>
5 </Root>

 

 

從這個結果中我們可以看到對應的屬性中有了xmlns屬性,並且值就是我們賦給它的命名空間。

 

二、查詢並修改Xml

Linq to xml不僅僅是創建xml簡單,在查詢,編輯和刪除方面一樣是非常方便的。下面我們就會介紹這些。

首先我們創建一個QueryXml類,並在其中寫入如下的屬性

1         public static String Path
2         {
3             get
4             {
5                 String path = String.Format("{0}\\test1.xml", Environment.CurrentDirectory);
6                 return path;
7             }
8         }

 

 

同時在該路徑下新建一個test1.xml文件,並在其中寫入如下內容

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <Item v1="1" v2="2">Item1</Item>
4   <Item v1="1" v2="2" >Item2</Item>
5 </Root>

 

下面我們就可以正式開始了。

 

1.通過文件讀取xml

既然我們要對xml查詢就需要讀取對應的xml文件,當然后面會介紹其他的方式。

 

代碼如下:

 1         /// <summary>
 2         /// 通過文件讀取xml
 3         /// </summary>
 4         public static void QueryElementByFile()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             XElement xele1 = xele.Element("Item");
 8             Console.Write(xele1.Value.Trim());
 9             Console.ReadKey();
10         }

 

 

我們可以利用XElement的靜態方法Load讀取指定路徑下的xml文件,這里我們不僅讀取了該xml文件,同時還獲取的該xml的第一個item的值並輸出。

 

所以我們可以看到如下的結果:

 

2.在指定節點前后添加新節點

上面我們僅僅只是讀取xml以及簡單的查詢,下面我們不僅僅查詢並且還要在該節點前后插入新的節點。

 

代碼如下:

 1         /// <summary>
 2         /// 在指定節點前后添加新節點
 3         /// </summary>
 4         public static void AddToElementAfterAndBefore()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Elements("Item")
 8                         where ele.Value.Equals("Item2")
 9                         select ele).SingleOrDefault();
10             if (item != null)
11             {
12                 XElement nele = new XElement("NItem", "NItem");
13                 XElement nele2 = new XElement("BItem", "BItem");
14                 item.AddAfterSelf(nele);
15                 item.AddBeforeSelf(nele2);
16                 xele.Save(Path);
17             }
18         }

 

 

我們簡單的分析一下上面的代碼,首先我們利用linq從中查詢Item的值為Item2的節點,然后獲取其中第一個節點,然后通過AddAfterSelfAddBeforeSelf在該節點的后面和前面分別添加新的節點。

 

添加完之后的xml結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <Item v1="1" v2="2">Item1</Item>
4   <BItem>BItem</BItem>
5   <Item v1="1" v2="2">Item2</Item>
6   <NItem>NItem</NItem>
7 </Root>

 

 

3.添加屬性到節點中

我們已經可以動態的添加節點,但是創建的時候不僅僅可以創建節點,並且還能創建屬性,下面我們可以通過SetAttributeValue去添加新的屬性或者修改現有屬性。

 

代碼如下:

 1         /// <summary>
 2         /// 添加屬性到節點中
 3         /// </summary>
 4         public static void AddAttributeToEle()
 5         {
 6             XElement xele = XElement.Parse(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注釋-->
 7 <Item v1='1' v2='2'>Item1</Item><!--后面的注釋--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
 8             var item = (from ele in xele.Elements("Item")
 9                         where ele.Value.Equals("Item2")
10                         select ele).SingleOrDefault();
11             item.SetAttributeValue("v3", "3");
12             xele.Save(Path);
13         }

 

 

我們可以明顯的看出,這里我們已經不是使用XElement.Load去讀取xml文件,而是通過直接讀取xml字符串。接着我們還是跟上面一樣去查詢,然后通過SetAttributeValue添加了新的屬性,並保存。

 

Xml內容如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <!--前面的注釋-->
4   <Item v1="1" v2="2">Item1</Item>
5   <!--后面的注釋-->
6   <Item v1="1" v2="2" v3="3">Item2</Item>
7 </Root>

 

 

我們可以看到第二個Item中多了一個 v3=”3” 新的屬性。

 

4.添加注釋到指定節點前后

這里的語法基本跟添加節點到指定節點前后是相似的,只是讀取xml的方式不同。

 

代碼如下:

 1         /// <summary>
 2         /// 添加注釋到節點前后
 3         /// </summary>
 4         public static void AddCommentToAfterAndBefore()
 5         {
 6             TextReader tr = new StringReader(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的注釋-->
 7 <Item v1='1' v2='2'>Item1</Item><!--后面的注釋--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
 8             XElement xele = XElement.Load(tr);
 9             var item = (from ele in xele.Elements("Item")
10                         where ele.Value.Equals("Item1")
11                         select ele).FirstOrDefault();
12             if (item != null)
13             {
14                 XComment xcom = new XComment("后面的注釋");
15                 XComment xcoma = new XComment("前面的注釋");
16                 item.AddAfterSelf(xcom);
17                 item.AddBeforeSelf(xcoma);
18             }
19             tr.Close();
20             xele.Save(Path);
21         }

 

 

上面我使用StringReaderTextReader讀取xml字符串並使用XElement.Load讀取該對象,然后就是在新建節點的時候新建的是注釋節點,最后利用一樣的語法添加到指定節點前后。

 

最終結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <!--前面的注釋-->
4   <!--前面的注釋-->
5   <Item v1="1" v2="2">Item1</Item>
6   <!--后面的注釋-->
7   <!--后面的注釋-->
8   <Item v1="1" v2="2" v3="3">Item2</Item>
9 </Root>

 

 

5.替換指定節點

修改節點的值通過SetValue即可做到,但是有時涉及到子節點,而我們想一次性全部替換掉,那么我們就需要使用ReplaceWith

 

代碼如下:

 1         /// <summary>
 2         /// 替換指定節點
 3         /// </summary>
 4         public static void ReplaceElement()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Elements("Item")
 8                         where ele.Value.Equals("Item2")
 9                         select ele).FirstOrDefault();
10             if (item != null)
11             {
12                 item.ReplaceWith(new XElement("Item", "Item3"));
13             }
14             xele.Save(Path);
15         }

 

 

這里的重點在於ReplaceWith方法,調用該方法會發生兩個操作。首先是刪除該節點,然后在該節點的位置上將我們的節點插入完成替換。

 

最后的xml結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <!--前面的注釋-->
4   <!--前面的注釋-->
5   <Item v1="1" v2="2">Item1</Item>
6   <!--后面的注釋-->
7   <!--后面的注釋-->
8   <Item>Item3</Item>
9 </Root>

 

 

這樣我們很輕易的就替換了整個節點。

 

6.刪除指定屬性

前面我們介紹了創建、修改和添加屬性,但是還沒有介紹如何刪除指定的屬性,下面我們就通過一個簡單的實例來演示。

 

代碼如下:

 1         /// <summary>
 2         /// 刪除指定屬性
 3         /// </summary>
 4         public static void RemoveAttribute()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Elements("Item")
 8                         where ele.Value.Equals("Item1")
 9                         select ele).FirstOrDefault().Attribute("v1");
10             if (item != null)
11             {
12                 item.Remove();
13             }
14             xele.Save(Path);
15         }

 

 

我們首先查詢出指定的節點,然后指定某個屬性,最后調用XAttributeRemove方法既可。

 

結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <!--前面的注釋-->
4   <!--前面的注釋-->
5   <Item v2="2">Item1</Item>
6   <!--后面的注釋-->
7   <!--后面的注釋-->
8   <Item>Item3</Item>
9 </Root>

 

 

7.刪除指定節點

既然上面已經可以刪除屬性,自然也少不了刪除屬性。

 

代碼如下所示:

 1         /// <summary>
 2         /// 刪除指定節點
 3         /// </summary>
 4         public static void RemoveElement()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Elements("Item")
 8                         where ele.Value.Equals("Item1")
 9                         select ele).FirstOrDefault();
10             if (item != null)
11             {
12                 item.Remove();
13             }
14             xele.Save(Path);
15         }

 

 

依然是調用同樣的方法。

 

結果如下:

1 <?xml version="1.0" encoding="utf-8"?>
2 <Root>
3   <!--前面的注釋-->
4   <!--前面的注釋-->
5   <!--后面的注釋-->
6   <!--后面的注釋-->
7   <Item>Item3</Item>
8 </Root>

 

 

三、按節點關系查詢

上面的查詢都是通過相關的條件進行查詢,但是我們有時僅僅只需要通過之間的關系即可,這樣反而可以避免很多的代碼,當然稍加探索可以發現其實XElement都提供給我們了。

我們依然要新建一個StructureXml類,並在其中新建一個屬性。

 

如下所示:

1         public static String Path
2         {
3             get
4             {
5                 String path = String.Format("{0}\\test2.xml", Environment.CurrentDirectory);
6                 return path;
7             }
8         }

 

 

同時在該文件夾下新建一個test2.xml並寫入如下內容:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <Root>
 3   <Item>
 4     <SubItem1>
 5       1
 6     </SubItem1>
 7     <SubItem>
 8       <Child>
 9         sss
10       </Child>
11     </SubItem>
12     <SubItem2>
13       2
14     </SubItem2>
15   </Item>
16 </Root>

 

 

1.顯示指定節點的所有父節點

通過上面的xml文件,我們清晰的看出xml是具有結構性的,彼此之間都存在關系,而現在我們需要顯示某個節點的父級節點的名稱。

 

代碼如下所示:

 1         /// <summary>
 2         /// 顯示指定節點的所有父節點
 3         /// </summary>
 4         public static void ShowAllParentEle()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Descendants("Child")
 8                         select ele).FirstOrDefault();
 9             if (item != null)
10             {
11                 foreach (var sub in item.Ancestors())
12                 {
13                     Console.WriteLine(sub.Name);
14                 }
15                 Console.WriteLine("----------------");
16                 foreach (var sub in item.AncestorsAndSelf())
17                 {
18                     Console.WriteLine(sub.Name);
19                 }
20                 Console.ReadKey();
21             }
22         }

 

 

其中我們通過Descendants獲取最底的節點,然后使用Ancestors獲取所有的父級節點,而AncestorsAndSelf則表示包含本身。

 

最終結果如下所示:

 

我們從圖中看出,分割線前顯示的是不包含本身的,而下面是包含本身的。

 

2.顯示指定節點的所有子節點

我們不僅僅可以輸出一個節點的所有父級節點,同樣也可以輸出一個節點的所有子節點。

 

代碼如下所示:

 1         /// <summary>
 2         /// 顯示指定節點的所有子節點
 3         /// </summary>
 4         public static void ShowAllChildEle()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             foreach (var sub in xele.Descendants())
 8             {
 9                 Console.WriteLine(sub.Name);
10             }
11             Console.WriteLine("-----------------");
12             foreach (var sub in xele.DescendantsAndSelf())
13             {
14                 Console.WriteLine(sub.Name);
15             }
16             Console.ReadKey();
17         }

 

 

這里我們依然是分成輸出子級節點以及包含自己的。

 

結果如下所示:

 

3.顯示同級節點之前的節點

既然有了父子關系,當然也少不了同級關系,首先我們先顯示同級節點之前的節點。

 

代碼如下所示:

 1         /// <summary>
 2         /// 顯示同級節點之前的節點
 3         /// </summary>
 4         public static void ShowPrevEle()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Descendants("SubItem")
 8                         select ele).FirstOrDefault();
 9             if (item != null)
10             {
11                 foreach (var sub in item.ElementsBeforeSelf())
12                 {
13                     Console.WriteLine(sub.Name);
14                 }
15             }
16             Console.ReadKey();
17         }

 

 

這里我們看到我們通過ElementsBeforeSelf獲取該節點之前的同級節點,當然我們還可以傳入參數作為限制條件。這里我們通過查詢獲取了SubItem這個節點,並顯示該節點之前的同級節點。

 

最終結果如下:

 

4.顯示同級節點后面的節點

作為上面的補充。

 

代碼如下所示:

 1        /// <summary>
 2         /// 顯示同級節點后面的節點
 3         /// </summary>
 4         public static void ShowNextEle()
 5         {
 6             XElement xele = XElement.Load(Path);
 7             var item = (from ele in xele.Descendants("SubItem")
 8                         select ele).FirstOrDefault();
 9             if (item != null)
10             {
11                 foreach (var sub in item.ElementsAfterSelf())
12                 {
13                     Console.WriteLine(sub.Name);
14                 }
15             }
16             Console.ReadKey();
17         }

 

 

最終結果如下所示:

 

四、監聽xml事件

你可能會疑惑xml為什么還要監聽,其實這樣是有意義的,比如你要根據某個節點的值作為依賴,那么你就要監聽這個節點,如果這個節點發生改變的時候,

你才可以及時的作出反應。但是xml的事件監聽有一個特點,跟瀏覽器中的DOM事件類似,監聽父節點同樣也可以監聽的到它的子節點的事件。下面我們

通過一個簡單的實例來說明。

 

實例代碼如下:

 1     public static class EventXml
 2     {
 3         public static void BindChangeing()
 4         {
 5             XElement xele = new XElement("Root");
 6             xele.Changing += xele_Changing;
 7             xele.Changed += xele_Changed;
 8             xele.Add(new XElement("Item", "123"));
 9             var item = xele.Element("Item");
10             item.ReplaceWith(new XElement("Item", "2"));
11             item = xele.Element("Item");
12             item.Remove();
13             Console.ReadKey();
14         }
15 
16         static void xele_Changed(object sender, XObjectChangeEventArgs e)
17         {
18             XElement ele = sender as XElement;
19             Console.WriteLine(String.Format("已完成 {0}-{1}", ele.Name, e.ObjectChange));
20         }
21 
22         static void xele_Changing(object sender, XObjectChangeEventArgs e)
23         {
24             XElement ele = sender as XElement;
25             Console.WriteLine(String.Format("正在進行中 {0}-{1}", ele.Name, e.ObjectChange));
26         }
27 }

 

 

其中的關鍵就是ChangingChanged事件,其次就是在事件中判斷事件的來源。

 

最終結果如下所示:

 

五、處理xml流

在實際的商業化的開發中,xml不可能僅僅保存這么點數據。有可能保存着非常多的數據。但是我們還是按照以往的方式,就會將xml全部讀取進內存。

這樣會占據很多內存,影響系統的性能,針對這種情況我們需要使用流的方式去處理xml,因為流會按照我們的順序讀取部分xml進內存,並不會將所

xml都讀取進內存。

 

Xml文件內容如下所示:

1 <?xml version="1.0" encoding="utf-8" ?>
2 <Root>
3   <SubItem>1</SubItem>
4   <SubItem>1</SubItem>
5   <SubItem>1</SubItem>
6   <Item>A</Item>
7   <SubItem>1</SubItem>
8   <Item>B</Item>
9 </Root>

 

 

代碼如下所示:

 1     public static class ReadXmlStream
 2     {
 3         public static String Path
 4         {
 5             get
 6             {
 7                 String path = String.Format("{0}\\test3.xml", Environment.CurrentDirectory);
 8                 return path;
 9             }
10         }
11 
12         /// <summary>
13         /// 流式處理XML
14         /// </summary>
15         public static void ReadXml()
16         {
17             XmlReader reader = XmlReader.Create(Path);
18             while (reader.Read())
19             {
20                 if (reader.NodeType == XmlNodeType.Element && reader.Name.Equals("Item"))
21                 {
22                     XElement ele = XElement.ReadFrom(reader) as XElement;
23                     Console.WriteLine(ele.Value.Trim());
24                 }
25             }
26             Console.ReadKey();
27         }
28 }

 

 

這里我們通過XmlReaderCreate靜態方法打開xml文件,並通過Read一個節點的進行讀取,並判斷該節點的類型。

 

最終結果如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM