C# 之 Linq to Xml
▪ 前言
我相信很多從事 .NET 開發的,在 .NET 3.5 之前操作XML會比較麻煩,但是在此之後出現了 Linq to Xml,而今天的主人公就是 Linq to Xml,廢話不多說,直接進入主題。
一、生成Xml
為了能夠在結構有一定的組織,筆者建議大家新建一個控制檯專案,並且新建一個 CreateXml
類(以下程式碼都屬於該類中)。
並在其中寫入以下屬性:
public static String Path
{
get
{
return String.Format("{0}\\test.xml", Environment.CurrentDirectory);
}
}
這句程式碼很好理解,就是為了下面我們示例的時候可以將 xml 儲存到當前程式的執行路徑下。
1. 建立簡單的 xml
首先我們先練練手,建立一個簡單的 xml 並儲存到一個檔案中。
/// <summary>
/// 建立簡單的xml並儲存
/// </summary>
public static void CreateElement()
{
XDocument xdoc = new XDocument(
// 建立宣告
new XDeclaration("1.0", "utf-8", "yes"),
// 建立節點
new XElement("root",
new XElement("item", "1"),
new XElement("item", "2")
)
);
// 儲存內容
xdoc.Save(Path);
}
很多學習過 XML 的人可以從結構就能夠猜測出最終的 xml 的組織,而這也是 linq to xml 的優點之一。
這個函式首先建立一個 xml 文件,並設定該 xml 的版本為 1.0
,採用 utf-8
編碼,後面的 yes
表示該xml是獨立的。下面就開始建立每個節點的,首先是 Root
節點,然後在 Root
節點中新增兩個 Item
節點。
最終生成的 xml 如下所示:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
<item>1</item>
<item>2</item>
</root>
2. 建立註釋
當 xml 有很多項時,我們就需要利用註釋加以區別,通過 linq to xml 我們一樣可以在其中新增註釋。
比如下面這段程式碼:
/// <summary>
/// 建立註釋
/// </summary>
public static void CreateComment()
{
XDocument xdoc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("提示"),
new XElement("item", "asd")
);
// 儲存內容
xdoc.Save(Path);
}
這裡我們直接在版本資訊的後面新增了一條註釋。
最終的結果如下所示:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--提示-->
<item>asd</item>
3. 根據物件建立xml
很多時候我們都會將陣列之類的型別轉換成 xml 以便儲存進永久性儲存介質中,所以下面我們也簡單的舉了一個例子,將陣列轉換成 xml。
程式碼如下所示:
/// <summary>
/// 根據物件建立xml並儲存
/// </summary>
public static void CreateElementByObjects()
{
// 初始化
var s = Enumerable.Range(1, 10);
// 構建 XML 元素
XElement xele = new XElement(
"Root",
from item in s select new XElement("item", item.ToString())
);
// 儲存內容
xele.Save(Path);
}
一開始的程式碼 var s = Enumerable.Radge(1,10)
是從1開始遞增,生成含有10項的陣列,以便後面我們進行新增,有了這個陣列之後,我們通過簡單的 linq 語句將陣列轉換成 xml,新增到Root中。
儲存之後的結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</Root>
4. 建立屬性
有時我們不想建立新的子項去儲存資料,而是使用屬性的方式去儲存。理所應當,linq to xml 一樣也支援這個功能,下面我們可以通過簡單的語句去實現它。
/// <summary>
/// 建立屬性
/// </summary>
public static void CreteAttribute()
{
// 初始化
XAttribute xa = new XAttribute("V2", "2");
// 構建 XML 元素
XElement xele = new XElement(
"Root",
new XElement("Item", new XAttribute("V1", "1"), xa)
);
// 儲存內容
xele.Save(Path);
}
我們依然可以看到熟悉的語法,這裡我們利用了 XAttribute
去建立一個屬性,並新增到 XElement
中。
最終的結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item V1="1" V2="2" />
</Root>
5. 建立名稱空間
對於一些企業級的 xml 格式,會非常的嚴格。特別是在同一個 xml 中可能會出現重複的項,但是我們又想區分開來,這個時候我們可以利用名稱空間將他們分開(跟C#中的名稱空間類似。)。
下面是建立名稱空間的示例:
/// <summary>
/// 建立名稱空間
/// </summary>
public static void CreateNamespace()
{
// 構建 XML 元素
XElement xele = new XElement("{http://www.xamarin-cn.com}Root",
new XElement("Item", "1"),
new XElement("{http://www.baidu.com}Item", 2)
);
// 儲存內容
xele.Save(Path);
}
結果如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://www.xamarin-cn.com">
<Item xmlns="">1</Item>
<Item xmlns="http://www.baidu.com">2</Item>
</Root>
從這個結果中我們可以看到對應的屬性中有了xmlns屬性,並且值就是我們賦給它的名稱空間。
二、查詢並修改Xml
Linq to xml 不僅僅是建立 xml 簡單,在查詢,編輯和刪除方面一樣是非常方便的,下面我們就會介紹這些。
首先我們建立一個 QueryXml
類,並在其中寫入如下的屬性:
public static String Path
{
get
{
return String.Format("{0}\\test1.xml", Environment.CurrentDirectory);
}
}
同時在該路徑下新建一個 test1.xml
檔案,並在其中寫入如下內容:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item v1="1" v2="2">Item1</Item>
<Item v1="1" v2="2">Item2</Item>
</Root>
下面我們就可以正式開始了。
1. 通過檔案讀取xml
既然我們要對 xml 查詢就需要讀取對應的 xml 檔案,當然後面會介紹其他的方式。
程式碼如下:
/// <summary>
/// 通過檔案讀取xml
/// </summary>
public static void QueryElementByFile()
{
XElement xele = XElement.Load(Path);
XElement xeleItem = xele.Element("Item");
Console.Write(xeleItem.Value.Trim());
Console.ReadKey();
}
我們可以利用 XElement
的靜態方法 Load
讀取指定路徑下的 xml 檔案,這裡我們不僅讀取了該 xml 檔案,同時還獲取的該 xml 的第一個 Item
的值並輸出。
所以我們可以看到如下的結果:
Item1
2. 在指定節點前後新增新節點
上面我們僅僅只是讀取xml以及簡單的查詢,下面我們不僅僅查詢並且還要在該節點前後插入新的節點。
程式碼如下:
/// <summary>
/// 在指定節點前後新增新節點
/// </summary>
public static void AddToElementAfterAndBefore()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Elements("Item") where ele.Value.Equals("Item2") select ele).SingleOrDefault();
if( item != null ){
XElement nele = new XElement("NItem", "NItem");
XElement nele2 = new XElement("BItem", "BItem");
item.AddAfterSelf(nele);
item.AddBeforeSelf(nele2);
xele.Save(Path);
}
}
xele.Elements("Item")
表示獲取xele
元素的下級節點中名稱為Item
的節點
我們簡單的分析一下上面的程式碼,首先我們利用 Linq 從中查詢 Item
的值為 Item2
的節點,然後獲取其中第一個節點,然後通過 AddAfterSelf
和 AddBeforeSelf
在該節點的後面和前面分別新增新的節點。
新增完之後的xml結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item v1="1" v2="2">Item1</Item>
<BItem>BItem</BItem>
<Item v1="1" v2="2">Item2</Item>
<NItem>NItem</NItem>
</Root>
3. 新增屬性到節點中
我們已經可以動態的新增節點,但是建立的時候不僅僅可以建立節點,並且還能建立屬性,下面我們可以通過 SetAttributeValue
去新增新的屬性或者修改現有屬性。
程式碼如下:
/// <summary>
/// 新增屬性到節點中
/// </summary>
public static void AddAttributeToEle()
{
XElement xele = XElement.Parse(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的註釋-->
<Item v1='1' v2='2'>Item1</Item><!--後面的註釋--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
var item = (from ele in xele.Elements("Item") where ele.Value.Equals("Item2") select ele).SingleOrDefault();
item.SetAttributeValue("v3", "3");
xele.Save(Path);
}
我們可以明顯的看出,這裡我們已經不是使用 XElement.Load
去讀取 xml 檔案,而是通過直接讀取 xml 字串。接著我們還是跟上面一樣去查詢,然後通過 SetAttributeValue
新增了新的屬性,並儲存。
Xml內容如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的註釋-->
<Item v1="1" v2="2">Item1</Item>
<!--後面的註釋-->
<Item v1="1" v2="2" v3="3">Item2</Item>
</Root>
我們可以看到第二個 Item
中多了一個 v3=”3”
新的屬性。
4. 新增註釋到指定節點前後
這裡的語法基本跟新增節點到指定節點前後是相似的,只是讀取 xml 的方式不同。
程式碼如下:
/// <summary>
/// 新增註釋到節點前後
/// </summary>
public static void AddCommentToAfterAndBefore()
{
TextReader tr = new StringReader(@"<?xml version='1.0' encoding='utf-8'?><Root><!--前面的註釋-->
<Item v1='1' v2='2'>Item1</Item><!--後面的註釋--><Item v1='1' v2='2' v3='3'>Item2</Item></Root>");
XElement xele = XElement.Load(tr);
var item = (from ele in xele.Elements("Item") where ele.Value.Equals("Item1") select ele).FirstOrDefault();
if( item != null ){
XComment xcom = new XComment("後面的註釋");
XComment xcoma = new XComment("前面的註釋");
item.AddAfterSelf(xcom);
item.AddBeforeSelf(xcoma);
}
tr.Close();
xele.Save(Path);
}
上面我使用 StringReader
和 TextReader
讀取 xml 字串並使用 XElement.Load
讀取該物件,然後就是在新建節點的時候新建的是註釋節點,最後利用一樣的語法新增到指定節點前後。
最終結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的註釋-->
<!--前面的註釋-->
<Item v1="1" v2="2">Item1</Item>
<!--後面的註釋-->
<!--後面的註釋-->
<Item v1="1" v2="2" v3="3">Item2</Item>
</Root>
5. 替換指定節點
修改節點的值通過 SetValue
即可做到,但是有時涉及到子節點,而我們想一次性全部替換掉,那麼我們就需要使用 ReplaceWith
。
程式碼如下:
/// <summary>
/// 替換指定節點
/// </summary>
public static void ReplaceElement()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Elements("Item")
where ele.Value.Equals("Item2")
select ele).FirstOrDefault();
if (item != null)
{
item.ReplaceWith(new XElement("Item", "Item3"));
}
xele.Save(Path);
}
這裡的重點在於 ReplaceWith
方法,呼叫該方法會發生兩個操作。首先是刪除該節點,然後在該節點的位置上將我們的節點插入完成替換。
最後的xml結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的註釋-->
<!--前面的註釋-->
<Item v1="1" v2="2">Item1</Item>
<!--後面的註釋-->
<!--後面的註釋-->
<Item>Item3</Item>
</Root>
這樣我們很輕易的就替換了整個節點。
6. 刪除指定屬性
前面我們介紹了建立、修改和新增屬性,但是還沒有介紹如何刪除指定的屬性,下面我們就通過一個簡單的例項來演示。
程式碼如下:
/// <summary>
/// 刪除指定屬性
/// </summary>
public static void RemoveAttribute()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Elements("Item")
where ele.Value.Equals("Item1")
select ele).FirstOrDefault().Attribute("v1");
if (item != null)
{
item.Remove();
}
xele.Save(Path);
}
我們首先查詢出指定的節點,然後指定某個屬性,最後呼叫 XAttribute 的 Remove 方法既可。
結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的註釋-->
<!--前面的註釋-->
<Item v2="2">Item1</Item>
<!--後面的註釋-->
<!--後面的註釋-->
<Item>Item3</Item>
</Root>
7. 刪除指定節點
既然上面已經可以刪除屬性,自然也少不了刪除屬性。
程式碼如下所示:
/// <summary>
/// 刪除指定節點
/// </summary>
public static void RemoveElement()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Elements("Item")
where ele.Value.Equals("Item1")
select ele).FirstOrDefault();
if (item != null)
{
item.Remove();
}
xele.Save(Path);
}
依然是呼叫同樣的方法。
結果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的註釋-->
<!--前面的註釋-->
<!--後面的註釋-->
<!--後面的註釋-->
<Item>Item3</Item>
</Root>
三、按節點關係查詢
上面的查詢都是通過相關的條件進行查詢,但是我們有時僅僅只需要通過之間的關係即可,這樣反而可以避免很多的程式碼,當然稍加探索可以發現其實 XElement
都提供給我們了。
我們依然要新建一個 StructureXml
類,並在其中新建一個屬性。
如下所示:
public static String Path
{
get
{
return String.Format("{0}\\test2.xml", Environment.CurrentDirectory);
}
}
同時在該資料夾下新建一個 test2.xml
並寫入如下內容:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Item>
<SubItem1>1</SubItem1>
<SubItem>
<Child>sss</Child>
</SubItem>
<SubItem2>2</SubItem2>
</Item>
</Root>
1. 顯示指定節點的所有父節點
通過上面的 xml 檔案,我們清晰的看出 xml 是具有結構性的,彼此之間都存在關係,而現在我們需要顯示某個節點的父級節點的名稱。
程式碼如下所示:
/// <summary>
/// 顯示指定節點的所有父節點
/// </summary>
public static void ShowAllParentEle()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Descendants("Child") select ele).FirstOrDefault();
if( item != null ){
foreach( var sub in item.Ancestors() ){
Console.WriteLine(sub.Name);
}
Console.WriteLine("----------------");
foreach( var sub in item.AncestorsAndSelf() ){
Console.WriteLine(sub.Name);
}
Console.ReadKey();
}
}
其中我們通過 Descendants
獲取最底的節點,然後使用 Ancestors
獲取所有的父級節點,而 AncestorsAndSelf
則表示包含本身。
最終結果如下所示:
SubItem
Item
Root
----------------
Child
SubItem
Item
Root
我們從上面的結果中可以看出,分割線前顯示的是不包含本身的,而下面是包含本身的。
2. 顯示指定節點的所有子節點
我們不僅僅可以輸出一個節點的所有父級節點,同樣也可以輸出一個節點的所有子節點。
程式碼如下所示:
/// <summary>
/// 顯示指定節點的所有子節點
/// </summary>
public static void ShowAllChildEle()
{
XElement xele = XElement.Load(Path);
foreach( var sub in xele.Descendants() ){
Console.WriteLine(sub.Name);
}
Console.WriteLine("-----------------");
foreach( var sub in xele.DescendantsAndSelf() ){
Console.WriteLine(sub.Name);
}
Console.ReadKey();
}
這裡我們依然是分成輸出子級節點以及包含自己的。
結果如下所示:
Item
SubItem1
SubItem
Child
SubItem2
-----------------
Root
Item
SubItem1
SubItem
Child
SubItem2
3. 顯示同級節點之前的節點
既然有了父子關係,當然也少不了同級關係,首先我們先顯示同級節點之前的節點。
程式碼如下所示:
/// <summary>
/// 顯示同級節點之前的節點
/// </summary>
public static void ShowPrevEle()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Descendants("SubItem") select ele).FirstOrDefault();
if( item != null ){
foreach( var sub in item.ElementsBeforeSelf() ){
Console.WriteLine(sub.Name);
}
}
Console.ReadKey();
}
這裡我們看到我們通過 ElementsBeforeSelf
獲取該節點之前的同級節點,當然我們還可以傳入引數作為限制條件。這裡我們通過查詢獲取了 SubItem
這個節點,並顯示該節點之前的同級節點。
最終結果如下:
SubItem1
4. 顯示同級節點後面的節點
作為上面的補充。
程式碼如下所示:
/// <summary>
/// 顯示同級節點後面的節點
/// </summary>
public static void ShowNextEle()
{
XElement xele = XElement.Load(Path);
var item = (from ele in xele.Descendants("SubItem") select ele).FirstOrDefault();
if( item != null ){
foreach( var sub in item.ElementsAfterSelf() ){
Console.WriteLine(sub.Name);
}
}
Console.ReadKey();
}
最終結果如下所示:
SubItem2
四、監聽xml事件
你可能會疑惑xml為什麼還要監聽,其實這樣是有意義的,比如你要根據某個節點的值作為依賴,那麼你就要監聽這個節點,如果這個節點發生改變的時候,你才可以及時的作出反應。
但是 xml 的事件監聽有一個特點,跟瀏覽器中的 DOM 事件類似,監聽父節點同樣也可以監聽的到它的子節點的事件。下面我們通過一個簡單的例項來說明。
例項程式碼如下:
public static class EventXml
{
public static void BindChangeing()
{
XElement xele = new XElement("Root");
xele.Changed += xele_Changed;
xele.Changing += xele_Changing;
xele.Add(new XElement("Item", "123"));
var item = xele.Element("Item");
item.ReplaceWith(new XElement("Item", "2"));
item = xele.Element("Item");
item.Remove();
Console.ReadKey();
}
static void xele_Changed(object sender, XObjectChangeEventArgs e)
{
XElement ele = sender as XElement;
Console.WriteLine(String.Format("已完成 {0}-{1}", ele.Name, e.ObjectChange));
}
static void xele_Changing(object sender, XObjectChangeEventArgs e)
{
XElement ele = sender as XElement;
Console.WriteLine(String.Format("正在進行中 {0}-{1}", ele.Name, e.ObjectChange));
}
}
其中的關鍵就是Changing和Changed事件,其次就是在事件中判斷事件的來源。
最終結果如下所示:
正在進行中 Item-Add
已完成 Item-Add
正在進行中 Item-Remove
已完成 Item-Remove
正在進行中 Item-Add
已完成 Item-Add
正在進行中 Item-Remove
已完成 Item-Remove
五、處理xml流
在實際的商業化的開發中,xml不可能僅僅儲存這麼點資料。有可能儲存著非常多的資料。但是我們還是按照以往的方式,就會將xml全部讀取進記憶體。
這樣會佔據很多記憶體,影響系統的效能,針對這種情況我們需要使用流的方式去處理xml,因為流會按照我們的順序讀取部分xml進記憶體,並不會將所
有xml都讀取進記憶體。
Xml檔案內容如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<SubItem>1</SubItem>
<SubItem>1</SubItem>
<SubItem>1</SubItem>
<Item>A</Item>
<SubItem>1</SubItem>
<Item>B</Item>
</Root>
程式碼如下所示:
public static class ReadXmlStream
{
public static String Path
{
get
{
String path = String.Format("{0}\\test3.xml", Environment.CurrentDirectory);
return path;
}
}
/// <summary>
/// 流式處理XML
/// </summary>
public static void ReadXml()
{
XmlReader reader = XmlReader.Create(Path);
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name.Equals("Item"))
{
XElement ele = XElement.ReadFrom(reader) as XElement;
Console.WriteLine(ele.Value.Trim());
}
}
Console.ReadKey();
}
}
這裡我們通過XmlReader的Create靜態方法開啟xml檔案,並通過Read一個節點的進行讀取,並判斷該節點的型別。
最終結果如下:
A
B
相關文章
- C#規範整理·集合和LinqC#
- C# LINQ (語言整合查詢)C#
- C# XML解析C#XML
- C#版本LINQ增強開源庫C#
- C# Lambda表示式和linq表示式 之 匿名物件查詢接收C#物件
- C# 操作xml(轉)C#XML
- 如何在C#中除錯LINQ查詢C#除錯
- 重學c#系列——linq(2) [二十八]C#
- 重學c#系列——linq(3) [二十九]C#
- C# Linq 延遲查詢的執行C#
- C#中Linq的去重方式Distinct詳解C#
- XML基本操作-建立(DOM和LOINQ)和LINQ查詢和儲存XML
- C#讀取Xml檔案C#XML
- C# 將HTML轉為XMLC#HTMLXML
- C# 將Excel轉為XMLC#ExcelXML
- 【轉】LINQ to SQL語句(1)之WhereSQL
- 使用C#讀寫xml檔案C#XML
- Linq
- python之XML解析PythonXML
- C# - XML讀寫與序列化C#XML
- c#(解析xml檔案基礎方法)C#XML
- 【C#進階】LINQ和資料庫操作_2024-06-22C#資料庫
- C# 中使用Linq和Lambda表示式對List進行排序C#排序
- C# XML基礎入門(XML檔案內容增刪改查清)C#XML
- C# xml文件反序列化記事C#XML
- Lambda、Linq
- Qt學習之XMLQTXML
- java基礎之XMLJavaXML
- Python XML解析之DOMPythonXML
- XML安全之Web ServicesXMLWeb
- 【C#】一個喜歡用Python的菜狗在嘗試Linq之後總結的常見用法以及示例C#Python
- linQ基礎
- 瞭解LINQ
- linq介紹
- [go-linq]-Go的.NET LINQ式查詢方法Go
- .NETCore C# 中級篇2-6 Json與XMLNetCoreC#JSONXML
- 【spring原始碼系列】之【xml解析】Spring原始碼XML
- LinQ查詢基礎(三)LINQ to ADO.net(1)LINQ to DataSet實現複雜資料查詢