C#中的Linq to Xml詳解
前言
我相信很多從事.NET開發(fā)的,在.NET 3.5之前操作XML會比較麻煩,但是在此之后出現(xiàn)了Linq to Xml,而今天的主人公就是Linq to Xml,廢話不多說,直接進(jìn)入主題。
一、生成Xml
為了能夠在結(jié)構(gòu)有一定的組織,筆者建議大家新建一個控制臺項目,并且新建一個CreateXml類(以下部分都屬于該類中)。
并在其中寫入以下屬性:
public static String Path
{
get
{
String path = String.Format("{0}\\test.xml", Environment.CurrentDirectory);
return path;
}
}
這句代碼很好理解,就是為了下面我們示例的時候可以將xml保存到當(dāng)前程序的運(yùn)行路徑下。
(以下的示例中不會包含Main方法中的寫法,因為Main中僅僅只要調(diào)用該靜態(tài)方法即可。)
1.創(chuàng)建簡單的Xml
首先我們先練練手,創(chuàng)建一個簡單的Xml并保存到一個文件中。
代碼如下:
/// <summary>
/// 創(chuàng)建簡單的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);
}
很多學(xué)習(xí)過XML的人可以從結(jié)構(gòu)就能夠猜測出最終的xml的組織,而這也是linq to xml的優(yōu)點(diǎn)之一。這句代碼首先創(chuàng)建一個xml文檔,并設(shè)置該xml的版本為1.0,
采用utf-8編碼,后面的yes表示該xml是獨(dú)立的。下面就開始創(chuàng)建每個節(jié)點(diǎn)的,首先是Root節(jié)點(diǎn),然后在Root節(jié)點(diǎn)中添加兩個Item節(jié)點(diǎn)。
最終生成的Xml如下所示:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
<item>1</item>
<item>2</item>
</root>
2.創(chuàng)建注釋
當(dāng)xml有很多項時,我們就需要利用注釋加以區(qū)別,通過linq to xml我們一樣可以在其中添加注釋。
比如下面這段代碼:
/// <summary>
/// 創(chuàng)建注釋
/// </summary>
public static void CreateComment()
{
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("提示"),
new XElement("item", "asd")
);
doc.Save(Path);
}
這里我們直接在版本信息的后面添加了一條注釋。
最終的結(jié)果如下所示:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--提示-->
<item>asd</item>
3.根據(jù)對象創(chuàng)建xml
很多時候我們都會將數(shù)組之類的類型轉(zhuǎn)換成xml以便保存進(jìn)永久性存儲介質(zhì)中,所以下面我們也簡單的舉了一個例子,將數(shù)組轉(zhuǎn)換成xml。
代碼如下所示:
/// <summary>
/// 根據(jù)對象創(chuàng)建xml并保存
/// </summary>
public static void CreateElementByObjects()
{
var s = Enumerable.Range(1, 10);
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項的數(shù)組,以便后面我們進(jìn)行添加,有了這個數(shù)組之后,
我們通過簡單的linq語句將數(shù)組轉(zhuǎn)換成xml,添加到Root中。
保存之后的結(jié)果如下:
<?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.創(chuàng)建屬性
有時我們不想創(chuàng)建新的子項去保存數(shù)據(jù),而是使用屬性的方式去保存。理所應(yīng)當(dāng),linq to xml一樣也支持這個功能,下面我們可以通過簡單的語句去實現(xiàn)它。
代碼如下所示:
/// <summary>
/// 創(chuàng)建屬性
/// </summary>
public static void CreteAttribute()
{
XAttribute xa = new XAttribute("V2", "2");
XElement xele = new XElement(
"Root",
new XElement("Item",
new XAttribute("V1", "1"),
xa
));
xele.Save(Path);
}
我們依然可以看到熟悉的語法,這里我們利用了XAttribute去創(chuàng)建一個屬性,并添加到XElement中。
最終的結(jié)果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item V1="1" V2="2" />
</Root>
5.創(chuàng)建命名空間
對于一些企業(yè)級的xml格式,會非常的嚴(yán)格。特別是在同一個xml中可能會出現(xiàn)重復(fù)的項,但是我們又想?yún)^(qū)分開來,這個時候我們可以利用命名空間將他們分開(跟C#中的命名空間類似。)。
下面是創(chuàng)建命名空間的示例:
/// <summary>
/// 創(chuàng)建命名空間
/// </summary>
public static void CreateNamespace()
{
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);
}
結(jié)果如下所示:
<?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>
從這個結(jié)果中我們可以看到對應(yīng)的屬性中有了xmlns屬性,并且值就是我們賦給它的命名空間。
二、查詢并修改Xml
Linq to xml不僅僅是創(chuàng)建xml簡單,在查詢,編輯和刪除方面一樣是非常方便的。下面我們就會介紹這些。
首先我們創(chuàng)建一個QueryXml類,并在其中寫入如下的屬性:
public static String Path
{
get
{
String path = String.Format("{0}\\test1.xml", Environment.CurrentDirectory);
return path;
}
}
同時在該路徑下新建一個test1.xml文件,并在其中寫入如下內(nèi)容:
<?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查詢就需要讀取對應(yīng)的xml文件,當(dāng)然后面會介紹其他的方式。
代碼如下:
/// <summary>
/// 通過文件讀取xml
/// </summary>
public static void QueryElementByFile()
{
XElement xele = XElement.Load(Path);
XElement xele1 = xele.Element("Item");
Console.Write(xele1.Value.Trim());
Console.ReadKey();
}
我們可以利用XElement的靜態(tài)方法Load讀取指定路徑下的xml文件,這里我們不僅讀取了該xml文件,同時還獲取的該xml的第一個item的值并輸出。
所以我們可以看到如下的結(jié)果:
2.在指定節(jié)點(diǎn)前后添加新節(jié)點(diǎn)
上面我們僅僅只是讀取xml以及簡單的查詢,下面我們不僅僅查詢并且還要在該節(jié)點(diǎn)前后插入新的節(jié)點(diǎn)。
代碼如下:
/// <summary>
/// 在指定節(jié)點(diǎn)前后添加新節(jié)點(diǎn)
/// </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);
}
}
我們簡單的分析一下上面的代碼,首先我們利用linq從中查詢Item的值為Item2的節(jié)點(diǎn),然后獲取其中第一個節(jié)點(diǎn),然后通過AddAfterSelf和AddBeforeSelf在該節(jié)點(diǎn)的后面和前面分別添加新的節(jié)點(diǎn)。
添加完之后的xml結(jié)果如下:
<?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.添加屬性到節(jié)點(diǎn)中
我們已經(jīng)可以動態(tài)的添加節(jié)點(diǎn),但是創(chuàng)建的時候不僅僅可以創(chuàng)建節(jié)點(diǎn),并且還能創(chuàng)建屬性,下面我們可以通過SetAttributeValue去添加新的屬性或者修改現(xiàn)有屬性。
代碼如下:
/// <summary>
/// 添加屬性到節(jié)點(diǎn)中
/// </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);
}
我們可以明顯的看出,這里我們已經(jīng)不是使用XElement.Load去讀取xml文件,而是通過直接讀取xml字符串。接著我們還是跟上面一樣去查詢,然后通過SetAttributeValue添加了新的屬性,并保存。
Xml內(nèi)容如下:
<?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.添加注釋到指定節(jié)點(diǎn)前后
這里的語法基本跟添加節(jié)點(diǎn)到指定節(jié)點(diǎn)前后是相似的,只是讀取xml的方式不同。
代碼如下:
/// <summary>
/// 添加注釋到節(jié)點(diǎn)前后
/// </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讀取該對象,然后就是在新建節(jié)點(diǎn)的時候新建的是注釋節(jié)點(diǎn),最后利用一樣的語法添加到指定節(jié)點(diǎn)前后。
最終結(jié)果如下:
<?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.替換指定節(jié)點(diǎn)
修改節(jié)點(diǎn)的值通過SetValue即可做到,但是有時涉及到子節(jié)點(diǎn),而我們想一次性全部替換掉,那么我們就需要使用ReplaceWith。
代碼如下:
/// <summary>
/// 替換指定節(jié)點(diǎn)
/// </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);
}
這里的重點(diǎn)在于ReplaceWith方法,調(diào)用該方法會發(fā)生兩個操作。首先是刪除該節(jié)點(diǎn),然后在該節(jié)點(diǎn)的位置上將我們的節(jié)點(diǎn)插入完成替換。
最后的xml結(jié)果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的注釋-->
<!--前面的注釋-->
<Item v1="1" v2="2">Item1</Item>
<!--后面的注釋-->
<!--后面的注釋-->
<Item>Item3</Item>
</Root>
這樣我們很輕易的就替換了整個節(jié)點(diǎn)。
6.刪除指定屬性
前面我們介紹了創(chuàng)建、修改和添加屬性,但是還沒有介紹如何刪除指定的屬性,下面我們就通過一個簡單的實例來演示。
代碼如下:
/// <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);
}
我們首先查詢出指定的節(jié)點(diǎn),然后指定某個屬性,最后調(diào)用XAttribute的Remove方法既可。
結(jié)果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的注釋-->
<!--前面的注釋-->
<Item v2="2">Item1</Item>
<!--后面的注釋-->
<!--后面的注釋-->
<Item>Item3</Item>
</Root>
7.刪除指定節(jié)點(diǎn)
既然上面已經(jīng)可以刪除屬性,自然也少不了刪除屬性。
代碼如下所示:
/// <summary>
/// 刪除指定節(jié)點(diǎn)
/// </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);
}
依然是調(diào)用同樣的方法。
結(jié)果如下:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<!--前面的注釋-->
<!--前面的注釋-->
<!--后面的注釋-->
<!--后面的注釋-->
<Item>Item3</Item>
</Root>
三、按節(jié)點(diǎn)關(guān)系查詢
上面的查詢都是通過相關(guān)的條件進(jìn)行查詢,但是我們有時僅僅只需要通過之間的關(guān)系即可,這樣反而可以避免很多的代碼,當(dāng)然稍加探索可以發(fā)現(xiàn)其實XElement都提供給我們了。
我們依然要新建一個StructureXml類,并在其中新建一個屬性。
如下所示:
public static String Path
{
get
{
String path = String.Format("{0}\\test2.xml", Environment.CurrentDirectory);
return path;
}
}
同時在該文件夾下新建一個test2.xml并寫入如下內(nèi)容:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Item>
<SubItem1>
1
</SubItem1>
<SubItem>
<Child>
sss
</Child>
</SubItem>
<SubItem2>
2
</SubItem2>
</Item>
</Root>
1.顯示指定節(jié)點(diǎn)的所有父節(jié)點(diǎn)
通過上面的xml文件,我們清晰的看出xml是具有結(jié)構(gòu)性的,彼此之間都存在關(guān)系,而現(xiàn)在我們需要顯示某個節(jié)點(diǎn)的父級節(jié)點(diǎn)的名稱。
代碼如下所示:
/// <summary>
/// 顯示指定節(jié)點(diǎn)的所有父節(jié)點(diǎn)
/// </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獲取最底的節(jié)點(diǎn),然后使用Ancestors獲取所有的父級節(jié)點(diǎn),而AncestorsAndSelf則表示包含本身。
最終結(jié)果如下所示:
我們從圖中看出,分割線前顯示的是不包含本身的,而下面是包含本身的。
2.顯示指定節(jié)點(diǎn)的所有子節(jié)點(diǎn)
我們不僅僅可以輸出一個節(jié)點(diǎn)的所有父級節(jié)點(diǎn),同樣也可以輸出一個節(jié)點(diǎn)的所有子節(jié)點(diǎn)。
代碼如下所示:
/// <summary>
/// 顯示指定節(jié)點(diǎn)的所有子節(jié)點(diǎn)
/// </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();
}
這里我們依然是分成輸出子級節(jié)點(diǎn)以及包含自己的。
結(jié)果如下所示:
3.顯示同級節(jié)點(diǎn)之前的節(jié)點(diǎn)
既然有了父子關(guān)系,當(dāng)然也少不了同級關(guān)系,首先我們先顯示同級節(jié)點(diǎn)之前的節(jié)點(diǎn)。
代碼如下所示:
/// <summary>
/// 顯示同級節(jié)點(diǎn)之前的節(jié)點(diǎn)
/// </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獲取該節(jié)點(diǎn)之前的同級節(jié)點(diǎn),當(dāng)然我們還可以傳入?yún)?shù)作為限制條件。這里我們通過查詢獲取了SubItem這個節(jié)點(diǎn),并顯示該節(jié)點(diǎn)之前的同級節(jié)點(diǎn)。
最終結(jié)果如下:
4.顯示同級節(jié)點(diǎn)后面的節(jié)點(diǎn)
作為上面的補(bǔ)充。
代碼如下所示:
/// <summary>
/// 顯示同級節(jié)點(diǎn)后面的節(jié)點(diǎn)
/// </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();
}
最終結(jié)果如下所示:
四、監(jiān)聽xml事件
你可能會疑惑xml為什么還要監(jiān)聽,其實這樣是有意義的,比如你要根據(jù)某個節(jié)點(diǎn)的值作為依賴,那么你就要監(jiān)聽這個節(jié)點(diǎn),如果這個節(jié)點(diǎn)發(fā)生改變的時候,
你才可以及時的作出反應(yīng)。但是xml的事件監(jiān)聽有一個特點(diǎn),跟瀏覽器中的DOM事件類似,監(jiān)聽父節(jié)點(diǎn)同樣也可以監(jiān)聽的到它的子節(jié)點(diǎn)的事件。下面我們
通過一個簡單的實例來說明。
實例代碼如下:
public static class EventXml
{
public static void BindChangeing()
{
XElement xele = new XElement("Root");
xele.Changing += xele_Changing;
xele.Changed += xele_Changed;
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("正在進(jìn)行中 {0}-{1}", ele.Name, e.ObjectChange));
}
}
其中的關(guān)鍵就是Changing和Changed事件,其次就是在事件中判斷事件的來源。
最終結(jié)果如下所示:
五、處理xml流
在實際的商業(yè)化的開發(fā)中,xml不可能僅僅保存這么點(diǎn)數(shù)據(jù)。有可能保存著非常多的數(shù)據(jù)。但是我們還是按照以往的方式,就會將xml全部讀取進(jìn)內(nèi)存。
這樣會占據(jù)很多內(nèi)存,影響系統(tǒng)的性能,針對這種情況我們需要使用流的方式去處理xml,因為流會按照我們的順序讀取部分xml進(jìn)內(nèi)存,并不會將所
有xml都讀取進(jìn)內(nèi)存。
Xml文件內(nèi)容如下所示:
<?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靜態(tài)方法打開xml文件,并通過Read一個節(jié)點(diǎn)的進(jìn)行讀取,并判斷該節(jié)點(diǎn)的類型。
最終結(jié)果如下:
相關(guān)文章
Unity3D實現(xiàn)攝像機(jī)鏡頭移動并限制角度
這篇文章主要為大家詳細(xì)介紹了Unity3D實現(xiàn)攝像機(jī)鏡頭移動并限制角度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05C#畢業(yè)設(shè)計之Winform零壓健身房管理系統(tǒng)
本文介紹了個人的《零壓健身房管理系統(tǒng)(扁平化)》的基本流程和功能點(diǎn)的介紹,虛心接受各位的意見,歡迎在提出寶貴的意見,大家一起探討學(xué)習(xí)2021-09-09C#由當(dāng)前日期計算相應(yīng)的周一和周日的實例代碼
這篇文章介紹了C#由當(dāng)前日期計算相應(yīng)的周一和周日的實例代碼,有需要的朋友可以參考一下2013-09-09