CodeGym /Các khóa học /C# SELF /Làm việc nâng cao với XML:

Làm việc nâng cao với XML: XmlDocumentXPath

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Hãy tưởng tượng một XML khổng lồ được gửi từ một dịch vụ SOAP hoặc banking API. Nó có hàng trăm phần tử lồng nhau, cấu trúc không đồng đều, và bạn chỉ cần tìm đúng một giá trị tổng tiền thanh toán của tháng trước. Cách làm bằng serialization ở đây không phù hợp — quá nặng và không biết trước phải nhìn vào đâu. Cần một "kính lúp" cho cây XML và kỹ năng đi nhanh trên các nhánh của nó.

Chính vì những trường hợp này trong .NET đã có một công cụ mạnh mẽ từ lâu — lớp XmlDocument, kết hợp với ngôn ngữ truy vấn XPath. Hôm nay bạn sẽ học:

  • Load XML vào DOM-tree;
  • Đi lại và tìm các phần tử cần bằng tay và bằng XPath;
  • Chỉnh sửa nội dung của XML document;
  • Thêm, xóa và thay đổi node;
  • Sử dụng XPath cho các truy vấn phức tạp.

2. XmlDocument là gì?

XmlDocument — lớp từ namespace System.Xml. Đây là một hiện thực của mô hình DOM (Document Object Model) — đại diện toàn bộ file XML dưới dạng cây đối tượng trong bộ nhớ.

Ít lý thuyết và phép so sánh

Nếu XmlSerializer giống như bộ LEGO, khi bạn biến object thành các miếng và ngược lại, thì XmlDocument giống như làm việc trực tiếp với một cái cây: có root, cành (elements), lá (text nodes), và bạn có thể đi trên cây đó, thêm cành, cắt lá và ghép chúng đi nơi khác tùy ý.

Load một XML đơn giản

Giả sử ta có XML như sau:

<users>
  <user id="1">
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>Igor</name>
    <age>35</age>
  </user>
</users>

Load nó vào bộ nhớ:

using System.Xml;

string xml = @"
<users>
  <user id='1'>
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id='2'>
    <name>Igor</name>
    <age>35</age>
  </user>
</users>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // hoặc doc.Load("duong_dan_file.xml");

Sau khi gọi LoadXml (hoặc Load nếu XML từ file) bạn có quyền truy cập đầy đủ vào nội dung document.

3. Dạo quanh DOM-tree

Cấu trúc DOM-tree

Mỗi document XML sau khi load trở thành một cây, gồm các node kiểu khác nhau:

Loại node Class trong .NET Ví dụ
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Olga, 28
Comment XmlComment
<!-- binh_luan -->

Để truy cập phần tử cần thiết — bạn sẽ "đi trên cây" sử dụng các property như ChildNodes, Attributes, ParentNode v.v.

Lấy element root

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

Duyệt các phần tử con

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — là <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"User {id}: {name}, age {age}");
    }
}

QUAN TRỌNG: Truy cập user["name"] chỉ hợp lệ nếu trong con trực tiếp có element <name>.

Truy cập attribute và text

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

4. Chỉnh sửa XML document

Thay đổi giá trị

Giả sử Olga bất ngờ muốn "trẻ hóa":

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // bây giờ <age>22</age>

Thêm người dùng mới

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

XmlElement name = doc.CreateElement("name");
name.InnerText = "Vasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

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

Xóa phần tử

Xóa user thứ hai ("Igor"):

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

Lưu thay đổi

doc.Save("users_updated.xml");
// Hoặc doc.OuterXml — để lấy chuỗi XML

5. XPath — ngôn ngữ tìm kiếm và chọn lọc trong XML

Làm việc với DOM có thể mệt nếu bạn cần tìm phần tử "theo điều kiện" — ví dụ, tất cả user lớn hơn 25 tuổi. Đó là lý do có XPath — ngôn ngữ điều hướng trên cây XML.

Sử dụng cơ bản XPath

Các truy vấn XPath có thể thực hiện qua phương thức SelectSingleNode (trả node đầu tiên) và SelectNodes (trả collection).

Ví dụ: tìm user có id = 1

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

Ví dụ: tìm tất cả user lớn hơn 25 tuổi

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // In ra Olga và Igor (nếu Igor chưa bị xóa)
}

XPath — cú pháp ngắn gọn

Biểu thức XPath Sẽ làm gì
/users/user
Tất cả phần tử <user> trong root <users>
user[@id='3']
User có id=3
user[age>25]
Users lớn hơn 25 tuổi
user/name
Tất cả element <name> của mọi user
user[last()]
User cuối cùng
user[position()<3]
Hai user đầu tiên

Nhiều ví dụ hơn: Tài liệu về XPath.

Thêm ví dụ: chọn lọc theo phần tử lồng nhau

Giả sử XML phức tạp hơn:

<library>
  <book>
    <title>Chúa ruồi</title>
    <author>
      <firstname>William</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>Trong hướng Svan</title>
    <author>
      <firstname>Marcel</firstname>
      <lastname>Proust</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);

Kết quả in ra sẽ là:

Golding
Proust

6. XPath: lọc, logic, tính toán

Lọc logic

In tên tất cả users có tuổi giữa 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);

Làm việc với attribute

Attribute được truy vấn bằng @. Tìm users có id bắt đầu bằng "1":

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

Đếm số node

Trực tiếp trong method SelectNodes không trả số bằng hàm count() — nó trả collection, không phải số. Nhưng bạn có thể làm như sau:

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

Lọc lồng nhau

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

7. XPath và namespace

Nếu XML của bạn dùng namespace (xmlns), mọi thứ trở nên thú vị hơn! Dùng XmlNamespaceManager cho các trường hợp đó:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Thuật toán</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. Chỉnh sửa XML bằng DOM

Thêm node "on the fly"

Thêm element <email> cho user đầu tiên:

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

Thay đổi attribute

olga.SetAttribute("id", "99"); // Bây giờ id="99"

Xóa node bằng XPath

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

9. Những mẹo hữu ích

Tìm và thay đổi cấu trúc hàng loạt

Giả sử bạn nhận một XML lớn với các phần tử khác nhau <record type="...">. Cần giữ lại chỉ những record có attribute type"customer", và thêm cho mỗi cái một child <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);
}

Cây node XML (DOM)


users
├─ user (id="1")
│   ├─ name ("Olga")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("Igor")
│   └─ age ("35")

Ví dụ chọn bằng XPath

XPath-query Sẽ trả
/users/user[1]
User đầu tiên (id="1")
/users/user[last()]
User cuối cùng (id="2")
/users/user[age>30]
Tất cả > 30 tuổi (Igor)
/users/user[@id='2']
User có id="2"

Ứng dụng thực tế

  • Tích hợp với API "cũ". Ở nhiều cơ quan nhà nước và ngân hàng SOAP/XML vẫn còn dùng nhiều. Khả năng nhanh tìm và sửa trong một XML lớn có thể cứu được hàng chục giờ làm việc.
  • Di chuyển dữ liệu. Khi chuyển hệ thống thường phải parse XML và làm các chọn lọc, chuyển đổi, và sửa hàng loạt.
  • Import-export vào Excel. Trong nhiều sản phẩm B2B input vẫn là XML. Ở đó XPath là cách nhanh để lấy data cần mà không cần dựng model lớn.

10. Lỗi, lưu ý và bẫy hay gặp

NullReferenceException: Nếu bạn cố truy cập element không tồn tại, ví dụ el["something"].InnerText, mà phần tử con đó hoàn toàn không có. Luôn kiểm tra null.

XPath và vị trí: Nhớ rằng biểu thức không có dấu / bắt đầu tìm trong descendants của node hiện tại, còn có / thì từ root của document. Các điểm tham chiếu khác nhau có thể dẫn đến không có kết quả mặc dù dữ liệu tồn tại.

Làm việc với namespace: Nếu không dùng XmlNamespaceManager, các truy vấn XPath tới XML có xmlns sẽ trả rỗng.

Chỉnh sửa trong khi duyệt: Nếu muốn xóa node dựa trên kết quả SelectNodes, hãy lưu chúng vào mảng trước, sau đó iterate — nếu không collection sẽ thay đổi khi duyệt và có thể gây lỗi.

Phân biệt text và element nodes: Giữa các element có thể có text nodes — khoảng trắng và xuống dòng, mà XML coi là nội dung. Để chọn chính xác dùng XmlElement hoặc lọc theo NodeType.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION