Làm việc với Cây Node Tài liệu
Làm việc với Cây Node Tài liệu
Hướng dẫn này cho thấy cách duyệt và thao tác cấu trúc của tài liệu trực tiếp thông qua cây node — mô hình đối tượng mà Document, Section, Paragraph, Run, Table, và mọi phần tử tài liệu khác được xây dựng trên. Mỗi phần tử trong tài liệu kế thừa từ Node, và mỗi phần tử có khả năng chứa con kế thừa từ CompositeNode, vì vậy việc hiểu hai lớp cơ sở này giải thích cách làm việc với nội dung mà chưa có trợ giúp viên chuyên dụng API nào bao phủ.
Các lớp cơ sở Node và CompositeNode
Node là lớp cơ sở trừu tượng cho mọi đối tượng trong cây tài liệu. Nó cung cấp NodeType (một giá trị enum xác định loại node này), ParentNode (các CompositeNode bao quanh), Document (tài liệu sở hữu), và PreviousSibling/NextSibling để di chuyển giữa các node cùng mức. Node.IsComposite báo cáo liệu một node nhất định có thể chứa các node con hay không. CompositeNode mở rộng Node cho các phần tử chứa — FirstChild, LastChild, và Count mô tả các node con trực tiếp của nó, và HasChildNodes báo cáo liệu có tồn tại bất kỳ node con nào. CompositeNode.GetChildNodes(nodeType, isDeep) lấy tất cả các node con của một NodeType cho trước, tùy chọn tìm kiếm toàn bộ subtree (isDeep) thay vì chỉ các node con trực tiếp, và CompositeNode.GetChild(nodeType, index, isDeep) lấy một node duy nhất theo chỉ mục.
Enum NodeType
NodeType liệt kê mọi loại node mà mô hình tài liệu định nghĩa — các loại cấu trúc như Document, Section, Body, Paragraph, Table, Row, và Cell; các loại nội dung nội tuyến như Run, Shape, và Comment; và các loại cơ chế trường như FieldStart, FieldSeparator, và FieldEnd, chúng đánh dấu ranh giới và văn bản chuyển đổi của một trường Word trong luồng run. Việc truyền một giá trị NodeType cho GetChildNodes hoặc GetChild giới hạn tìm kiếm chỉ tới các node có đúng loại đó, hoặc truyền NodeType.Any để khớp mọi loại node.
Duyệt và sửa đổi Cây
CompositeNode.GetEnumerator() cho phép bạn lặp qua các con trực tiếp của một nút, và CompositeNode cũng triển khai SelectNodes(xpath) và SelectSingleNode(xpath) cho các truy vấn dựa trên XPath trên toàn bộ cây con. Để sửa đổi cây, AppendChild(newChild) và PrependChild(newChild) thêm một nút vào đầu hoặc cuối danh sách các con, InsertBefore(newChild, refChild) và InsertAfter(newChild, refChild) chèn tương đối so với một nút con hiện có, và RemoveChild(oldChild) hoặc RemoveAllChildren() loại bỏ các nút. CompositeNode.IndexOf(child) trả về vị trí của một nút con. Trên Node tự nó, Clone(isCloneChildren) tạo một bản sao — tùy chọn sâu — và Remove() tách nút ra khỏi cha của nó. Node.NextPreOrder(rootNode) và PreviousPreOrder(rootNode) duyệt qua cây theo thứ tự tài liệu, giới hạn bởi một nút gốc cho trước, đây là cách phổ biến để quét toàn bộ cây con nút-theo-nút mà không cần mã đệ quy.
Bộ sưu tập Nút và Thông báo Thay đổi
NodeCollection là một bộ sưu tập nút cụ thể, có thể thay đổi, hỗ trợ Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node), và ToArray(), cùng với thuộc tính Count. NodeList và NodeEnumerator hỗ trợ việc lặp kiểu đọc trên kết quả truy vấn, chẳng hạn như các nút được SelectNodes trả về. Để quan sát các thay đổi cấu trúc khi chúng xảy ra, triển khai INodeChangingCallback — các phương thức NodeInserting(args), NodeInserted(args), NodeRemoving(args), và NodeRemoved(args) của nó mỗi đều nhận một NodeChangingArgs mô tả Node bị ảnh hưởng, OldParent/NewParent của nó, và NodeChangingAction đã xảy ra.
Thăm các nút bằng DocumentVisitor
Để xử lý mọi kiểu nút một cách có cấu trúc mà không phải tự mình đi qua cây, kế thừa DocumentVisitor và ghi đè các phương thức Visit*Start/Visit*End cho các kiểu nút mà bạn quan tâm — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun, v.v. — sau đó gọi Node.Accept(visitor) trên nút gốc của cây con để thực thi. Đây là mẫu chuẩn để xuất, chuyển đổi, hoặc kiểm tra một tài liệu mà không cần viết mã duyệt đệ quy tùy chỉnh cho mỗi trường hợp sử dụng.
Mẹo và Thực hành Tốt nhất
- Sử dụng
NodeType.Anycùng vớiGetChildNodes(NodeType.Any, true)khi bạn cần mọi phần tử con bất kể loại, thay vì gọiGetChildNodesmột lần cho mỗi loại. - Ưu tiên
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)để quét một cây con dạng phẳng, không đệ quy thay vì viết thủ công quá trình đi xuống đệ quy. - Triển khai
DocumentVisitorthay vì mã duyệt cây tạm thời khi cùng một xử lý cần chạy trên nhiều loại nút khác nhau. - Kiểm tra
Node.IsCompositetrước khi ép kiểu sangCompositeNodekhi làm việc với nút có kiểu không xác định. - Đăng ký một
INodeChangingCallbackkhi bạn cần phản hồi các việc chèn hoặc xóa do mã khác thực hiện (bao gồm cả các thao tác tích hợp), không chỉ những thay đổi mà mã của bạn tự tạo ra trực tiếp.
Các vấn đề thường gặp
| Vấn đề | Nguyên nhân | Khắc phục |
|---|---|---|
Chuyển đổi một Node sang CompositeNode gây ra lỗi invalid-cast | Nút là kiểu lá (ví dụ Run hoặc FieldStart) không thể chứa các nút con | Kiểm tra Node.IsComposite trước, hoặc sử dụng NodeType để xác nhận nút là kiểu container trước khi chuyển đổi |
GetChildNodes chỉ trả về các nút con trực tiếp | isDeep đã bị để false (hoặc bị bỏ qua khi overload mặc định là shallow) | Truyền isDeep: true để tìm kiếm toàn bộ cây con |
| Việc sửa đổi một collection trong khi lặp qua nó sẽ gây ra lỗi | Các node đã được thêm hoặc xóa bên trong một foreach trên NodeCollection hoặc kết quả của GetChildNodes | Materialize các node để sửa đổi bằng ToArray() trước, sau đó lặp qua mảng trong khi biến đổi cây |
Một subclass DocumentVisitor không được gọi cho một số nội dung | Visitor đã được gọi qua Accept() trên một node không chứa nội dung mục tiêu, hoặc phương thức Visit* tương ứng chưa được ghi đè | Gọi Accept() trên một ancestor chứa nội dung, và ghi đè mọi cặp Visit*Start/Visit*End liên quan đến các loại node đang được xử lý |
FAQ
Sự khác biệt giữa Node và CompositeNode là gì?
Node là lớp cơ sở mà mọi phần tử tài liệu kế thừa. CompositeNode mở rộng Node cho các phần tử có thể chứa các phần tử con (ví dụ như Body, Paragraph và Table); các phần tử lá (ví dụ như Run) kế thừa trực tiếp từ Node và không thể chứa phần tử con.
Làm thế nào để tôi tìm tất cả các nút có kiểu cụ thể trong một tài liệu?
Gọi CompositeNode.GetChildNodes(nodeType, isDeep) trên nút gốc tìm kiếm của bạn (thường là Document bản thân), truyền NodeType bạn muốn và isDeep: true để tìm kiếm toàn bộ cây con, không chỉ các con trực tiếp.
Làm sao tôi có thể xóa các nút một cách an toàn khi đang lặp qua chúng?
Đầu tiên thu thập các nút cần xóa vào một mảng — ví dụ bằng NodeCollection.ToArray() — sau đó lặp qua mảng đó và gọi Remove() trên mỗi nút, thay vì sửa đổi bộ sưu tập đang hoạt động trong khi một foreach đang lặp qua nó.
Các nút FieldStart, FieldSeparator và FieldEnd là gì?
Chúng là các giá trị NodeType đánh dấu cơ cấu của một trường Word nhúng trong luồng chạy: FieldStart và FieldEnd ràng buộc trường, và FieldSeparator tách mã của trường khỏi kết quả đang hiển thị hiện tại.
Làm sao tôi có thể xử lý mỗi đoạn, bảng và run trong một tài liệu?
Tạo lớp con của DocumentVisitor, ghi đè VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, và VisitRun cho các loại nút bạn cần, sau đó gọi Accept(visitor) trên Document (hoặc bất kỳ nút gốc cây con nào) để thực thi.
API Reference Tóm tắt
| Class/Method | Mô tả |
|---|---|
Node | Lớp cơ sở trừu tượng cho mọi nút trong cây tài liệu |
CompositeNode | Lớp cơ sở cho các nút có thể chứa các nút con |
CompositeNode.GetChildNodes(nodeType, isDeep) | Lấy tất cả các nút con thuộc kiểu nhất định, tùy chọn trên toàn bộ cây con |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Thêm một nút vào container ở cuối, hoặc trước/sau một nút con hiện có |
NodeType | Enum xác định loại của một nút (Paragraph, Table, Run, FieldStart, và các giá trị khác) |
NodeCollection | Bộ sưu tập cụ thể, có thể thay đổi của các đối tượng Node |
Node.Clone(isCloneChildren) | Sao chép một nút, tùy chọn bao gồm các nút con của nó |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Duyệt qua một nhánh con theo thứ tự tài liệu |
INodeChangingCallback | Giao diện callback được thông báo khi các nút được chèn hoặc xóa |
DocumentVisitor | Lớp cơ sở cho việc duyệt có cấu trúc, phân phát theo kiểu thông qua Node.Accept(visitor) |