CodeGym /課程 /C# SELF /進階處理 XML: XmlDocument

進階處理 XML: XmlDocumentXPath

C# SELF
等級 48 , 課堂 2
開放

1. 介紹

想像你拿到一個超大的 XML,是某個 SOAP 服務或銀行 API 回傳的。裡面有成百上千個巢狀元素、結構不一,而你只需要找到上個月的一筆付款金額。把它序列化成物件在這種情況不太適合——太笨重,也不知道要往哪看。你需要一把針對 XML 樹的「放大鏡」,以及快速在樹上搜尋的能力。

.NET 已經存在一個很強的工具好幾年了——類別 XmlDocument,搭配查詢語言 XPath。今天你會學會:

  • 把 XML 載入成 DOM 樹;
  • 手動或用 XPath 在樹上導航並找到元素;
  • 修改 XML 文件內容;
  • 新增、刪除、變更節點;
  • XPath 做複雜的選取。

2. 什麼是 XmlDocument?

XmlDocument 是來自命名空間 System.Xml 的類別。它是 DOM (Document Object Model) 的實作——把整個 XML 檔案表示成記憶體裡的一棵物件樹。

一點理論與比喻

如果 XmlSerializer 像是在把物件組成 LEGO,能把物件拆成一堆磚塊然後再組起來,那麼 XmlDocument 就更像直接在操作真實的樹:有根、分支(元素)、葉子(文字節點),你可以在樹上走動、增加分支、修剪葉子或把它們挪到別的地方。

簡單載入 XML 文件

假設我們有這樣的 XML:

<users>
  <user id="1">
    <name>奧爾嘉</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>伊戈爾</name>
    <age>35</age>
  </user>
</users>

把它載入記憶體:

using System.Xml;

string xml = @"
<users>
  <user id='1'>
    <name>奧爾嘉</name>
    <age>28</age>
  </user>
  <user id='2'>
    <name>伊戈爾</name>
    <age>35</age>
  </user>
</users>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // 或者 doc.Load("路徑_到_檔案.xml");

呼叫 LoadXml(或從檔案用 Load)之後,你就可以完整地存取文件內容了。

3. 在 DOM 樹上導航

DOM 樹的結構

每個載入後的 XML 文件會成為一棵樹,由不同類型的節點組成:

節點類型 .NET 類別 範例
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText 奧爾嘉, 28
Comment XmlComment
<!-- 註解 -->

要存取需要的元素,你得在樹上「走動」,使用屬性像 ChildNodesAttributesParentNode 等等。

取得根元素

XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users

遍歷子元素

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — 這是 <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"使用者 {id}: {name}, 年齡 {age}");
    }
}

重要:存取 user["name"] 只有在直接子節點中有 <name> 元素時才可行。

存取屬性與文字

var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "奧爾嘉"

4. 修改 XML 文件

變更值

假設奧爾嘉突然決定要「變年輕」:

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // 現在 <age>22</age>

新增使用者

XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");

XmlElement name = doc.CreateElement("name");
name.InnerText = "瓦西莉莎";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);

刪除元素

刪掉第二個使用者(「伊戈爾」):

XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
    root.RemoveChild(userToDelete);

儲存變更

doc.Save("users_updated.xml");
// 或者 doc.OuterXml — 取得 XML 字串

5. XPath — 在 XML 中搜尋與選取的語言

當你需要根據條件找元素時,操作 DOM 樹會很累——比如找出所有年齡超過 25 的使用者。這時就用得上 XPath——一種在 XML 樹上導航的語言。

基本使用 XPath

可以透過方法 SelectSingleNode(回傳第一個符合的節點)和 SelectNodes(回傳節點集合)來執行 XPath 查詢。

範例:找出 id = 1 的使用者

XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // 奧爾嘉

範例:找出所有年齡大於 25 的使用者

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // 會列出奧爾嘉和伊戈爾(如果伊戈爾還沒被刪掉)
}

XPath — 簡單語法

XPath 表達式 執行結果
/users/user
根節點 <users> 下的所有 <user> 元素
user[@id='3']
id=3 的使用者
user[age>25]
年齡大於 25 的使用者
user/name
所有使用者的 <name> 元素
user[last()]
最後一個 <user>
user[position()<3]
前兩個 <user>

更多範例與語法:XPath 文件

再一個範例:依巢狀元素選取

假設 XML 更複雜:

<library>
  <book>
    <title>蒼蠅王</title>
    <author>
      <firstname>威廉</firstname>
      <lastname>戈爾丁</lastname>
    </author>
  </book>
  <book>
    <title>斯萬之路</title>
    <author>
      <firstname>馬賽爾</firstname>
      <lastname>普魯斯特</lastname>
    </author>
  </book>
</library>
XmlDocument doc = new XmlDocument();
doc.LoadXml(libraryXml);
XmlNodeList authors = doc.SelectNodes("/library/book/author/lastname");
foreach (XmlNode lastName in authors)
    Console.WriteLine(lastName.InnerText);

輸出會是:

戈爾丁
普魯斯特

6. XPath:過濾、邏輯與計算

邏輯過濾

列出所有年齡在 1830 之間的使用者:

// [age>=18 and age<=30]
XmlNodeList youngUsers = root.SelectNodes("user[age>=18 and age<=30]");
foreach (XmlNode u in youngUsers)
    Console.WriteLine(u["name"].InnerText);

操作屬性

屬性用 @ 來存取。找出那些 id 開頭為 "1" 的使用者:

XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");

計算節點數量

在 C# 的 SelectNodes 方法裡不能直接使用 count() 取得數字——它會回傳集合而不是數字。但你可以這樣做:

int count = root.SelectNodes("user").Count;

巢狀過濾

XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='戈爾丁']");

7. XPath 和命名空間

如果你的 XML 使用命名空間(xmlns),事情會變得更有趣!這種情況下請用 XmlNamespaceManager

<catalog xmlns="http://books.example.com">
  <book>
    <title>演算法</title>
  </book>
</catalog>
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("b", "http://books.example.com");

XmlNodeList books = doc.SelectNodes("/b:catalog/b:book", nsmgr);
foreach (XmlNode book in books)
    Console.WriteLine(book.SelectSingleNode("b:title", nsmgr)?.InnerText);

8. 用 DOM 修改 XML

即時新增節點

給第一個使用者新增一個 <email> 元素:

XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);

變更屬性

olga.SetAttribute("id", "99"); // 現在 id="99"

XPath 刪除節點

XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
    root.RemoveChild(node);

9. 有用的小技巧

搜尋與批次修改結構

假設你收到一個很大的 XML,裡面有許多不同的 <record type="..."> 元素。你只想保留那些屬性 type 值為 "customer" 的,並且為每個加入一個子元素 <status>active</status>

XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
    XmlElement status = doc.CreateElement("status");
    status.InnerText = "active";
    record.AppendChild(status);
}

XML 節點樹 (DOM)


users
├─ user (id="1")
│   ├─ name ("奧爾嘉")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("伊戈爾")
│   └─ age ("35")

XPath 選取範例

XPath 查詢 會回傳什麼
/users/user[1]
第一個使用者 (id="1")
/users/user[last()]
最後一個使用者 (id="2")
/users/user[age>30]
所有年齡超過 30 的 (伊戈爾)
/users/user[@id='2']
id="2" 的使用者

實務應用

  • 與「老舊」API 的整合。在很多政府機構和銀行,SOAP/XML 仍廣泛使用。能快速在大型 XML 回應中查找與修改資料,能為你節省大量時間。
  • 資料遷移。從一個系統搬到另一個時,經常要解析 XML 做複雜選取、轉換與批次修改。
  • 與 Excel 的匯入匯出。在不少 B2B 產品裡,輸入仍然是 XML。這時 XPath 是個快速擷取所需資料而不必建立大模型的好方法。

10. 錯誤、注意事項與常見陷阱

NullReferenceException:如果你試圖存取不存在的元素,例如 el["something"].InnerText,但那個子元素根本不存在,就會發生。記得一定要檢查 null

XPath 與巢狀層級:注意沒有起始 / 的表達式會在當前節點的子孫中搜尋,而有 / 的則從文件根開始。不同的參考點會導致找不到結果,即使資料其實存在。

處理命名空間:如果不使用 XmlNamespaceManager,對有 xmlns 的 XML 做 XPath 查詢通常會回傳空結果。

迭代時修改集合:如果你想依據 SelectNodes 的結果刪除節點,先把結果存到陣列再迭代——否則在迭代過程中集合會被修改,可能出錯。

文字節點與元素節點的差異:元素之間可能夾雜文字節點(空白和換行也會被 XML 當作內容)。若要嚴格選取,請只使用 XmlElement 或依據 NodeType 做過濾。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION