Bekerja dengan Pohon Node Dokumen
Bekerja dengan Pohon Node Dokumen
Panduan ini menunjukkan cara menavigasi dan memanipulasi struktur dokumen secara langsung melalui pohon node-nya — model objek yang Document, Section, Paragraph, Run, Table, dan setiap elemen dokumen lainnya dibangun di atasnya. Setiap elemen dalam dokumen diturunkan dari Node, dan setiap elemen yang dapat menampung anak diturunkan dari CompositeNode, sehingga memahami dua kelas dasar ini menjelaskan cara bekerja dengan konten yang belum ditangani oleh API khusus.
Kelas Dasar Node dan CompositeNode
Node adalah kelas dasar abstrak untuk setiap objek dalam pohon dokumen. Ia mengekspos NodeType (nilai enum yang mengidentifikasi jenis node ini), ParentNode (CompositeNode yang membungkus), Document (dokumen pemilik), dan PreviousSibling/NextSibling untuk berpindah antar node pada tingkat yang sama. Node.IsComposite melaporkan apakah sebuah node dapat menampung anak. CompositeNode memperluas Node untuk elemen kontainer — FirstChild, LastChild, dan Count menggambarkan anak langsungnya, dan HasChildNodes melaporkan apakah ada anak. CompositeNode.GetChildNodes(nodeType, isDeep) mengambil semua anak dari NodeType tertentu, secara opsional mencari seluruh subtree (isDeep) bukan hanya anak langsung, dan CompositeNode.GetChild(nodeType, index, isDeep) mengambil satu anak berdasarkan indeks.
Enum NodeType
NodeType mendaftar setiap jenis node yang didefinisikan oleh model dokumen — tipe struktural seperti Document, Section, Body, Paragraph, Table, Row, dan Cell; tipe konten inline seperti Run, Shape, dan Comment; serta tipe mesin field seperti FieldStart, FieldSeparator, dan FieldEnd, yang menandai batas dan teks saklar dari sebuah field Word dalam aliran run. Mengirimkan nilai NodeType ke GetChildNodes atau GetChild membatasi pencarian ke node dengan tipe tersebut secara tepat, atau kirim NodeType.Any untuk mencocokkan semua tipe node.
Menelusuri dan Memodifikasi Pohon
CompositeNode.GetEnumerator() memungkinkan Anda mengulangi anak langsung suatu node, dan CompositeNode juga mengimplementasikan SelectNodes(xpath) serta SelectSingleNode(xpath) untuk kueri berbasis XPath di seluruh subtree. Untuk memodifikasi pohon, AppendChild(newChild) dan PrependChild(newChild) menambahkan sebuah node di awal atau akhir anak-anak, InsertBefore(newChild, refChild) dan InsertAfter(newChild, refChild) menyisipkan relatif terhadap child yang ada, dan RemoveChild(oldChild) atau RemoveAllChildren() menghapus node. CompositeNode.IndexOf(child) mengembalikan posisi child. Pada Node sendiri, Clone(isCloneChildren) membuat salinan — opsional mendalam — dan Remove() melepaskan node dari induknya. Node.NextPreOrder(rootNode) dan PreviousPreOrder(rootNode) melangkah melalui pohon dalam urutan dokumen, dibatasi oleh node root tertentu, yang merupakan cara umum untuk memindai seluruh subtree node-per-node tanpa kode rekursif.
Koleksi Node dan Notifikasi Perubahan
NodeCollection adalah koleksi node konkret yang dapat diubah yang mendukung Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node), dan ToArray(), serta properti Count. NodeList dan NodeEnumerator mendukung iterasi gaya-baca atas hasil kueri, seperti node yang dikembalikan oleh SelectNodes. Untuk mengamati perubahan struktural secara real-time, implementasikan INodeChangingCallback — metode NodeInserting(args), NodeInserted(args), NodeRemoving(args), dan NodeRemoved(args) masing-masing menerima sebuah NodeChangingArgs yang menjelaskan Node yang terpengaruh, OldParent/NewParent-nya, dan NodeChangingAction yang terjadi.
Mengunjungi Node dengan DocumentVisitor
Untuk memproses setiap tipe node secara terstruktur tanpa harus menelusuri pohon secara manual, buat subclass DocumentVisitor dan timpa metode Visit*Start/Visit*End untuk tipe node yang Anda butuhkan — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun, dan seterusnya — kemudian panggil Node.Accept(visitor) pada root subtree untuk menjalankannya. Ini adalah pola standar untuk mengekspor, mentransformasi, atau memeriksa sebuah dokumen tanpa menulis traversal rekursif khusus untuk setiap kasus penggunaan.
Tips dan Praktik Terbaik
- Gunakan
NodeType.AnydenganGetChildNodes(NodeType.Any, true)ketika Anda membutuhkan setiap keturunan tanpa mempedulikan tipe, daripada memanggilGetChildNodessekali per tipe. - Lebih pilih
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)untuk pemindaian datar, non-rekursif pada subtree daripada menulis penurunan rekursif manual. - Implementasikan
DocumentVisitordaripada kode penelusuran pohon ad hoc ketika pemrosesan yang sama perlu dijalankan pada banyak tipe node yang berbeda. - Periksa
Node.IsCompositesebelum melakukan casting keCompositeNodesaat bekerja dengan node dengan tipe yang tidak diketahui. - Daftarkan sebuah
INodeChangingCallbackketika Anda perlu merespons penyisipan atau penghapusan yang dilakukan oleh kode lain (termasuk operasi bawaan), bukan hanya perubahan yang dibuat secara langsung oleh kode Anda sendiri.
Masalah Umum
| Masalah | Penyebab | Perbaikan |
|---|---|---|
Mencasting Node ke CompositeNode menghasilkan error invalid-cast | Node tersebut adalah tipe daun (misalnya Run atau FieldStart) yang tidak dapat memiliki anak | Periksa Node.IsComposite terlebih dahulu, atau gunakan NodeType untuk memastikan node adalah tipe kontainer sebelum mencasting |
GetChildNodes hanya mengembalikan anak langsung | isDeep dibiarkan false (atau dihilangkan di mana overload defaultnya adalah shallow) | Berikan isDeep: true untuk mencari seluruh subtree |
| Memodifikasi koleksi saat mengiterasinya menimbulkan kesalahan | Node ditambahkan atau dihapus di dalam foreach atas NodeCollection atau hasil dari GetChildNodes | Materialisasikan node yang akan dimodifikasi dengan ToArray() terlebih dahulu, lalu iterasi array sambil memutasi pohon |
Subkelas DocumentVisitor tidak dipanggil untuk beberapa konten | Visitor dipanggil melalui Accept() pada node yang tidak berisi konten target, atau metode Visit* yang bersangkutan tidak dioverride | Panggil Accept() pada nenek moyang yang berisi konten, dan override setiap pasangan Visit*Start/Visit*End yang relevan dengan tipe node yang diproses |
FAQ
Apa perbedaan antara Node dan CompositeNode?
Node adalah kelas dasar yang menjadi induk bagi semua elemen dokumen. CompositeNode memperluas Node untuk elemen yang dapat memiliki anak (seperti Body, Paragraph, dan Table); elemen daun (seperti Run) mewarisi langsung dari Node dan tidak dapat menampung anak.
Bagaimana cara saya menemukan semua node dengan tipe tertentu dalam sebuah dokumen?
Panggil CompositeNode.GetChildNodes(nodeType, isDeep) pada node yang menjadi akar pencarian Anda (seringkali Document itu sendiri), dengan memberikan NodeType yang Anda inginkan dan isDeep: true untuk mencari seluruh subtree, bukan hanya anak langsung.
Bagaimana cara menghapus node dengan aman saat iterasi?
Kumpulkan node yang akan dihapus ke dalam sebuah array terlebih dahulu — misalnya dengan NodeCollection.ToArray() — kemudian iterasi array tersebut dan panggil Remove() pada setiap node, alih-alih memodifikasi koleksi langsung saat foreach sedang mengiterasinya.
Apa itu node FieldStart, FieldSeparator, dan FieldEnd?
Mereka adalah nilai NodeType yang menandai mekanisme bidang Word yang disematkan dalam aliran run: FieldStart dan FieldEnd mengikat bidang tersebut, dan FieldSeparator memisahkan kode bidang dari hasil yang saat ini ditampilkan.
Bagaimana cara memproses setiap paragraf, tabel, dan run dalam sebuah dokumen?
Buat subclass DocumentVisitor, timpa VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, dan VisitRun untuk tipe node yang Anda butuhkan, lalu panggil Accept(visitor) pada Document (atau akar subtree mana pun) untuk menjalankannya.
API Reference Ringkasan
| Kelas/Metode | Deskripsi |
|---|---|
Node | Kelas dasar abstrak untuk setiap node dalam pohon dokumen |
CompositeNode | Kelas dasar untuk node yang dapat berisi anak |
CompositeNode.GetChildNodes(nodeType, isDeep) | Ambil semua anak dari tipe tertentu, secara opsional di seluruh subtree |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Tambahkan node ke container di akhir, atau sebelum/setelah child yang ada |
NodeType | Enum yang mengidentifikasi jenis node (Paragraph, Table, Run, FieldStart, dan seterusnya) |
NodeCollection | Koleksi konkret yang dapat diubah dari objek Node |
Node.Clone(isCloneChildren) | Salin node, secara opsional termasuk anak-anaknya |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Lalui subtree dalam urutan dokumen |
INodeChangingCallback | Antarmuka callback yang diberi notifikasi saat node disisipkan atau dihapus |
DocumentVisitor | Kelas dasar untuk penelusuran terstruktur, type-dispatched via Node.Accept(visitor) |