[入門][XML] XML入門系列 (2) : 以動態方式建立或產生 XML 文件

在本文中我將示範以不同的方式從無到有的建立一份 XML 文件...

在本文中我將示範以不同的方式從無到有的建立一份 XML 文件。

使用 XmlDocument 物件 

首先, 我們可以使用 XmlDocument 物件來一個節點一個節點的建立起 XML, 如下例:

using System.Xml;
....

// 建立一個 XmlDocument 物件並加入 Declaration
XmlDocument xdoc = new XmlDocument();
xdoc.AppendChild(xdoc.CreateXmlDeclaration("1.0", "UTF-8", "yes"));

// 建立根節點物件並加入 XmlDocument 中 (第 0 層)
XmlElement rootElement = xdoc.CreateElement("Employees");
xdoc.AppendChild(rootElement);

// 建立一個子元素, 並在這個子元素裡加上一個 attribute (第 1 層)
XmlElement eleChild1 = xdoc.CreateElement("Employee");
XmlAttribute attChild1 = xdoc.CreateAttribute("Department");
attChild1.Value = "研發部";
eleChild1.Attributes.Append(attChild1);
rootElement.AppendChild(eleChild1);

// 再為這個子元素加入一個孫元素 (第 2 層)
XmlElement eleGrandChild1 = xdoc.CreateElement("Name");
eleGrandChild1.InnerText= "吳大寶";
eleChild1.AppendChild(eleGrandChild1);

// 建立第二個子元素 (第 1 層)
XmlElement eleChild2 = xdoc.CreateElement("Employee");
XmlAttribute attChild2 = xdoc.CreateAttribute("Department");
attChild2.Value = "總務部";
eleChild2.Attributes.Append(attChild2);

// 建立第二個孫元素 (第 2 層)
XmlElement eleGrandChild2 = xdoc.CreateElement("Name");
eleGrandChild2.InnerText = "鄭小胖";
eleChild2.AppendChild(eleGrandChild2);
rootElement.AppendChild(eleChild2);

// 將建立的 XML 節點儲存為檔案
xdoc.Save(MapPath("~/App_Data/Test01.xml"));
Response.Write("XML created!");

以這個程式建立起來的 XML 文件內容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Employees>
  <Employee Department="研發部">
    <Name>吳大寶</Name>
  </Employee>
  <Employee Department="總務部">
    <Name>鄭小胖</Name>
  </Employee>
</Employees>

上面程式的寫法很直接, 你對照產生出來的 XML 內容就自然看得懂程式的邏輯了。唯一值得注意的一點是, 無論是 XmlElement 或 XmlAttribute 物件都一律是 reference type, 一旦被建立起來, 那麼不管你把它附加到哪裡幾次, 都不會自動建立新的副本 (instance)。例如, 當你建立好上例中第一個子元素後, 雖然你已把它加入根節點, 但你不能再把同一個子元素賦予新值或加入不同的孫節點後, 再重複加入根節點。就算你這麼做, 根節點中也不會因此產生兩個子節點 (原因已經說過了 - 程式並不會因為 Append 指令而為你自動產生新的副本)。因此在程式中, 我建立了不同 ID 的子節點、attribute 和孫節點。

使用 XmlDataDocument 物件 

除了使用 XmlDocument 物件來建立 XML 之外, 你也可以使用 XmlDataDocument 物件。不過 XmlDataDocument 和 XmlDocument 有很大的相似性; 在第一個程式中, 你只需要把第一行裡面的 XmlDocument 改成 XmlDataDocument 就行了:

XmlDataDocument xdoc = new XmlDataDocument();

程式的其它部份完全不用改,  其結果完全一樣。

使用神奇的 LINQ

自從 .Net Framework 3.5 (Visual Studio 2008) 之後, 我們就有 LINQ (Language-Integrated Query) 可以用了。LINQ 是一種函數式語言 (Functional Programming Language), 通常可以使用很直覺、很精簡的語法達到與其它複雜方法相同的功能。以建立 XML 這件事來說, LINQ 只需短短幾行程式就搞定了:

C# -

using System.Xml.Linq;
.... 

XDocument xdoc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"), 
    new XElement("Employees",
        new XElement("Employee", new XAttribute("Department", "研發部"),
            new XElement("Name", "吳大寶")),
        new XElement("Employee", new XAttribute("Department", "總務部"),
            new XElement("Name", "鄭小胖"))));
xdoc.Save(MapPath("~/App_Data/Test02.xml"));
Response.Write("XML created!");

如果你使用 VB 而非 C# 的話, 那麼, 恭喜你了, 因為若要在 VB 程式中憑空建立一個 XML 文件, 甚至根本無需任何繁文縟節, 你打什麼進去就是什麼了:

VB -

Dim xdoc = _
    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <Employees>
        <Employee Department="研發部">
            <Name>吳大寶</Name>
            <Phone>21</Phone>
        </Employee>
        <Employee Department="總務部">
            <Name>鄭小胖</Name>
            <Phone>25</Phone>
        </Employee>
    </Employees>
xdoc.Save(MapPath("~/App_Data/Test02.xml"))
Response.Write("XML created!")

以上程式所產生出來的 XML 文件和前面程式所產生者是一模一樣的, 但是我們可以看到 LINQ 程式的簡短和易懂, 其它方法難及項背。然而, LINQ 並不是沒有缺點; 一般來說, LINQ 的執行效率比較差。但是無論如何, LINQ 的優點是瑕不掩瑜的。

不過, 如果談到實用性, 那麼上面一段程式即使看起來精簡易懂, 它的實用性恐怕只有 1 (如果滿分是 5 的話)。為什麼這麼說呢? 因為除了是為了教學目的或建立架構等特殊目的之外, 請問你什麼時候會使用以上的方式來建立 XML 資料? 在實用面上, 我們多半只會以建立個別節點的方式來新增資料; 這時候, 你就會發現你還是會用到單獨的 XElement 和 XAttribute 物件, 而這種做法就和使用 XmlDocument 物件的做法差不多了。

但話又說回來, LINQ 確實因為語法上的精簡易懂, 而能夠節省我們開發上的時間。只要你不是使用 LINQ 在做繁雜而重複的工作, LINQ 在多數情況下仍然是我們最好的幫手。

在本站中我將不會特別針對 LINQ 做入門介紹, 主要是因為 LINQ 大概是我所看過的微軟技術中少數從一開始推出就有極大量教學及介紹的了。誠然, 如果要從理論開始學習, LINQ 可能有一點門檻, 但是 LINQ 本身既精簡卻又直覺, 我真的覺得沒有需要浪費太多篇幅在這個網路上已經有太多介紹的技術。所以我在我的所有文章中會直接使用它, 就不做入門介紹了。

加上 Namespace

在大部份情況下, 我們都不會在我們的 XML 文件中使用到 Namespace。一般來講, 只有當你的 XML 資料來源比較複雜, 而且你有需要將不同來源的 XML 資料彙集在同一份文件裡, 但是又擔心這些不同來源的元素中包含名稱相同而含意不同的元素時, 你才有需要要求這些 XML 資料通通標上 Namespace, 以利後續作業時可以唯一標示而不會紊亂。

若使用 LINQ, 那麼對各個元素加上 Namespace 的方法非常簡單, 但是其方式可能令人有點陌生:

using System.Xml.Linq;
....

XNamespace xns = "http://phone.idv.tw/cs2/";
XDocument xdoc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"),
    new XElement(xns + "Employees",
        new XElement(xns + "Employee", new XAttribute("Department", "研發部"),
            new XElement(xns + "Name", "吳大寶")),
        new XElement(xns + "Employee", new XAttribute("Department", "總務部"),
            new XElement(xns + "Name", "鄭小胖"))));
Response.Write(HttpUtility.HtmlEncode(
    xdoc.ToString()).Replace(" ", "&nbsp;").Replace("\r\n", "<br />"));
xdoc.Save(MapPath("~/App_Data/Test02.xml"));
Response.Write("XML created!");

VB 的程式則依然簡單, 自己把 xmlns=... 加上去就可以了:

Dim xdoc = _
    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <Employees xmlns="http://phone.idv.tw/cs2/">
        <Employee Department="研發部">
            <Name>吳大寶</Name>
            <Phone>21</Phone>
        </Employee>
        <Employee Department="總務部">
            <Name>鄭小胖</Name>
            <Phone>25</Phone>
        </Employee>
    </Employees>
Response.Write(HttpUtility.HtmlEncode(
    xdoc.ToString()).Replace(" ", "&nbsp;").Replace(VBNewLine, "<br />"))
xdoc.Save(MapPath("~/App_Data/Test02.xml"))
Response.Write("XML created!")

唯一需要特別記住的就是, 在 C# 中, 你一定要在每個元素的名稱之前都加上那個 XNamespace, 而它雖然並不是字串物件, 使用起來卻像個字串。如果你忘了對某個元素加上 Namespace, 那麼它仍然會有一個 Namespace, 但是是一個空字串。至於 VB 就沒有這個問題。

還有一件事值得留意。對 XElement 等物件, 使用 XElement.ToString() 會傳回 XML 資料的原始碼, 但如果你使用 (String) XElement 則會傳回 Value (如果這個 XElement 的值剛好是 String 型別的話), 這兩者的結果是不同的。同樣的, 如果你以類似 XElement x = new XElement("Pi", 3.1415926535) 這種方式建立一個 XElement 物件的話, 你可以使用 (double) x 取得它的值 (3.1415926535)。但如果使用 x.ToString(), 則會得到 <Pi>3.1415926535</Pi> 這樣的字串。

當你使用 (double) XElement 或 (string) XElement 以強制轉換型別時, 事實上 System.Xml.XmlConvert 以下的對應方法會被自動呼叫來進行轉換。這些方法提供了很大的彈性, 但如果差異太大 (例如企圖將 "ABC" 字串轉換為整數的話), 仍然會有 Exception 跑出來, 所以最好能謹慎使用。

載入 XML 文件

現在再回到實用性層面。剛才說過, 我們在現實生活中很少有機會從無到有的使用程式建立起一個 XML 文件。在大部份情況下, 這種事情我們只會做一次 (不管使用什麼方式, 包括以手動方式自己敲進去), 然後就會存成 XML 檔案。

所以, 我們每天會做的事情, 就是從既有的 XML 來源進行載入的動作, 而不必每次都從頭開始建立。如果使用 XmlDocument 或 XmlDataDocument 物件, 我們可以使用 Xmldocument.Load(檔案路徑) 讀入 XML 檔案, 或 XmlDocument.LoadXml(字串物件) 讀入 XML 字串。以下舉個簡單的例子:

VB -

Dim xdoc As New XmlDocument()
xdoc.Load(MapPath("~/App_Data/Test01.xml"))

C# -

XmlDocument xdoc = new XmlDocument();
xdoc.Load(MapPath("~/App_Data/Test01.xml"));

如果 XML 文件的來源不是檔案而是字串, 該怎麼讀入呢? 這時我們可以使用 MemoryStream 物件, 如下例:

Stream s = new MemoryStream(Encoding.UTF8.GetBytes(你要讀入的字串));
XmlReader reader = XmlTextReader.Create(s);
XElement ele = XElement.Load(reader);

載入 XML 文件之後, 我們要對它做些什麼事情呢? 那當然不外乎是進行節點的篩選、巡覽、新增、修改與刪除等等。這些部份, 我們會在後續的系列文章慢慢介紹。

以程式動態加入節點

前面提過, 在現實生活中, 我們很少使用程式以從頭建立一份文件, 但是以動態方式一個節點一個節點加入 XML 文件卻是常見的。以下我示範一個很簡單的方法, 可以使用 LINQ 將節點一個一個加上去。如果不使用 LINQ, 你仍然可以使用 XmlDocument 或 XmlDataDocument 來做到相同的事情, 雖然程式可能會稍為長一點, 但是一點都不難。

using System.Xml.Linq;
....

XDocument xdoc = createXmlDocument();
xdoc.Root.Add(createNode("研發部", "吳大寶"));
xdoc.Root.Add(createNode("總務部", "鄭小胖"));
xdoc.Save(MapPath("~/App_Data/Test02.xml"));
Response.Write("XML created!");
...
protected XDocument createXmlDocument()
{
    return new XDocument(new XDeclaration("1.0", "UTF-8", "yes"),
        new XElement("Employees"));
}
protected XElement createNode(string attValue, string childValue)
{
    return new XElement("Employee", new XAttribute("Department", attValue),
        new XElement("Name", childValue));
}

在程式中, 只需呼叫寫好的 createNode() 方法就可以依固定的結構建立並傳回一個 XML 節點了。

相關文章:

 


Dev 2Share @ 點部落