ทำงานกับต้นไม้โหนดของเอกสาร
ทำงานกับโหนดต้นไม้ของเอกสาร
คู่มือนี้แสดงวิธีการนำทางและจัดการโครงสร้างของเอกสารโดยตรงผ่านต้นไม้โหนด — โมเดลวัตถุที่ Document, Section, Paragraph, Run, Table และทุกองค์ประกอบอื่นของเอกสารถูกสร้างขึ้นบนพื้นฐานนี้ ทุกองค์ประกอบในเอกสารสืบทอดมาจาก Node และทุกองค์ประกอบที่สามารถมีลูกได้สืบทอดมาจาก CompositeNode ดังนั้นการเข้าใจสองคลาสฐานนี้จึงอธิบายวิธีการทำงานกับเนื้อหาที่ไม่มีตัวช่วยพิเศษ API ใดครอบคลุมอยู่แล้ว.
คลาสฐานของโหนดและ CompositeNode
Node เป็นคลาสฐานแบบนามธรรมสำหรับวัตถุทุกตัวในต้นไม้ของเอกสาร มันเปิดให้เข้าถึง NodeType (ค่าประเภท enum ที่ระบุประเภทของโหนดนี้), ParentNode (CompositeNode ที่ครอบคลุม), Document (เอกสารเจ้าของ), และ PreviousSibling/NextSibling สำหรับการย้ายระหว่างโหนดในระดับเดียวกัน Node.IsComposite รายงานว่าโหนดที่กำหนดสามารถมีลูกได้หรือไม่ CompositeNode ขยาย Node สำหรับองค์ประกอบประเภทคอนเทนเนอร์ — FirstChild, LastChild, และ Count อธิบายลูกโดยตรงของมัน, และ HasChildNodes รายงานว่ามีลูกอยู่หรือไม่ CompositeNode.GetChildNodes(nodeType, isDeep) ดึงข้อมูลลูกทั้งหมดของ NodeType ที่กำหนด, โดยสามารถค้นหาตลอด subtree ทั้งหมด (isDeep) แทนที่จะเป็นแค่ลูกโดยตรง, และ CompositeNode.GetChild(nodeType, index, isDeep) ดึงข้อมูลหนึ่งรายการตามดัชนี.
Enum NodeType
NodeType ระบุทุกประเภทของโหนดที่โมเดลเอกสารกำหนด — ประเภทโครงสร้างเช่น Document, Section, Body, Paragraph, Table, Row, และ Cell; ประเภทเนื้อหาในบรรทัดเช่น Run, Shape, และ Comment; และประเภทกลไกฟิลด์เช่น FieldStart, FieldSeparator, และ FieldEnd, ซึ่งทำเครื่องหมายขอบเขตและข้อความสวิตช์ของฟิลด์ Word ภายในสตรีมรัน การส่งค่า NodeType ไปยัง GetChildNodes หรือ GetChild จะจำกัดการค้นหาให้กับโหนดที่มีประเภทนั้นอย่างเท่านั้น, หรือส่ง NodeType.Any เพื่อจับคู่ทุกประเภทของโหนด.
การเดินสำรวจและแก้ไขต้นไม้
CompositeNode.GetEnumerator() ให้คุณวนลูปผ่านลูกโดยตรงของโหนด, และ CompositeNode ยังทำงาน SelectNodes(xpath) และ SelectSingleNode(xpath) สำหรับการสืบค้นแบบ XPath ทั่วทั้ง subtree. เพื่อแก้ไขต้นไม้, AppendChild(newChild) และ PrependChild(newChild) เพิ่มโหนดที่จุดเริ่มต้นหรือจุดสิ้นสุดของลูก, InsertBefore(newChild, refChild) และ InsertAfter(newChild, refChild) แทรกสัมพันธ์กับลูกที่มีอยู่, และ RemoveChild(oldChild) หรือ RemoveAllChildren() ลบโหนด. CompositeNode.IndexOf(child) คืนตำแหน่งของลูก. บน Node เอง, Clone(isCloneChildren) ทำสำเนา — สามารถทำเป็น deep ได้ — และ Remove() แยกโหนดออกจากพาเรนท์. Node.NextPreOrder(rootNode) และ PreviousPreOrder(rootNode) เดินผ่านต้นไม้ตามลำดับเอกสาร, จำกัดโดยโหนดรากที่กำหนด, ซึ่งเป็นวิธีทั่วไปในการสแกน subtree ทั้งหมดโหนดต่อโหนดโดยไม่ใช้โค้ดแบบ recursive.
คอลเลกชันโหนดและการแจ้งเตือนการเปลี่ยนแปลง
NodeCollection เป็นคอลเลกชันโหนดที่เป็นรูปธรรมและเปลี่ยนแปลงได้ รองรับ Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) และ ToArray(), พร้อมกับคุณสมบัติ Count. NodeList และ NodeEnumerator รองรับการวนลูปแบบอ่านผลลัพธ์การสืบค้น, เช่นโหนดที่คืนจาก SelectNodes. เพื่อสังเกตการเปลี่ยนแปลงโครงสร้างเมื่อเกิดขึ้น, ให้ทำการ implement INodeChangingCallback — วิธีการ NodeInserting(args), NodeInserted(args), NodeRemoving(args), และ NodeRemoved(args) ของมันจะรับ NodeChangingArgs ที่อธิบาย Node ที่ได้รับผลกระทบ, OldParent/NewParent ของมัน, และ NodeChangingAction ที่เกิดขึ้น.
เยี่ยมชมโหนดด้วย DocumentVisitor
เพื่อประมวลผลทุกประเภทโหนดในรูปแบบที่เป็นโครงสร้างโดยไม่ต้องเดินต้นไม้ด้วยตนเอง, ให้สร้าง subclass ของ DocumentVisitor และ override วิธีการ Visit*Start/Visit*End สำหรับประเภทโหนดที่คุณสนใจ — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun เป็นต้น — จากนั้นเรียก Node.Accept(visitor) ที่รากของ subtree เพื่อดำเนินการ. นี่เป็นรูปแบบมาตรฐานสำหรับการส่งออก, แปลง, หรือตรวจสอบเอกสารโดยไม่ต้องเขียนโค้ดการเดินแบบ recursive เฉพาะกรณีแต่ละกรณี.
เคล็ดลับและแนวปฏิบัติที่ดีที่สุด
- ใช้
NodeType.Anyพร้อมกับGetChildNodes(NodeType.Any, true)เมื่อคุณต้องการลูกทุกระดับโดยไม่สนใจประเภท, แทนที่จะเรียกGetChildNodesครั้งละประเภท. - แนะนำให้ใช้
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)สำหรับการสแกนแบบแบนและไม่ใช้ recursion ของ subtree แทนการเขียนการลงลึกแบบ recursive ด้วยตนเอง. - ใช้
DocumentVisitorแทนการเขียนโค้ดเดินต้นไม้แบบ ad hoc เมื่อการประมวลผลเดียวกันต้องทำงานกับหลายประเภทของโหนด. - ตรวจสอบ
Node.IsCompositeก่อนทำการแคสต์เป็นCompositeNodeเมื่อทำงานกับโหนดที่ไม่ทราบประเภท. - ลงทะเบียน
INodeChangingCallbackเมื่อคุณต้องการตอบสนองต่อการแทรกหรือการลบที่ทำโดยโค้ดอื่น (รวมถึงการดำเนินการในตัว) ไม่ใช่เพียงการเปลี่ยนแปลงที่โค้ดของคุณทำโดยตรง.
ปัญหาที่พบบ่อย
| ปัญหา | สาเหตุ | วิธีแก้ไข |
|---|---|---|
การแปลง Node เป็น CompositeNode ทำให้เกิดข้อผิดพลาดการแคสที่ไม่ถูกต้อง | โหนดเป็นประเภท leaf (เช่น Run หรือ FieldStart) ที่ไม่สามารถมีลูกได้ | ตรวจสอบ Node.IsComposite ก่อน หรือใช้ NodeType เพื่อยืนยันว่าโหนดเป็นประเภท container ก่อนทำการแคส |
GetChildNodes คืนค่าเฉพาะลูกโดยตรง | isDeep ถูกละไว้ false (หรือไม่ได้ระบุในกรณีที่ overload มีค่าเริ่มต้นเป็น shallow) | ส่ง isDeep: true เพื่อค้นหาทั้งต้นไม้ย่อยทั้งหมด |
| การแก้ไขคอลเลกชันขณะทำการวนซ้ำจะทำให้เกิดข้อผิดพลาด | โหนดถูกเพิ่มหรือเอาออกภายใน foreach ที่อยู่เหนือ NodeCollection หรือผลลัพธ์ของ GetChildNodes | สร้างโหนดที่ต้องการแก้ไขด้วย ToArray() ก่อน จากนั้นวนซ้ำผ่านอาร์เรย์ขณะทำการเปลี่ยนแปลงต้นไม้ |
คลาสย่อยของ DocumentVisitor ไม่ถูกเรียกสำหรับเนื้อหาบางส่วน | ผู้เยี่ยมชมถูกเรียกผ่าน Accept() บนโหนดที่ไม่มีเนื้อหาเป้าหมายอยู่ หรือเมธอด Visit* ที่สอดคล้องกันไม่ได้รับการเขียนทับ | เรียก Accept() บนโหนดบรรพบุรุษที่มีเนื้อหาอยู่ และเขียนทับคู่ Visit*Start/Visit*End ทุกคู่ที่เกี่ยวข้องกับประเภทโหนดที่กำลังประมวลผล |
FAQ
ความแตกต่างระหว่าง Node กับ CompositeNode คืออะไร?
Node คือคลาสฐานที่ทุกองค์ประกอบของเอกสารสืบทอดมาจาก CompositeNode ขยายจาก Node สำหรับองค์ประกอบที่สามารถมีลูกได้ (เช่น Body, Paragraph และ Table); ส่วนองค์ประกอบแบบ leaf (เช่น Run) สืบทอดโดยตรงจาก Node และไม่สามารถมีลูกได้.
ฉันจะหาทุกโหนดที่เป็นประเภทเฉพาะในเอกสารได้อย่างไร?
เรียก CompositeNode.GetChildNodes(nodeType, isDeep) บนโหนดที่เป็นรากของการค้นหาของคุณ (มักจะเป็น Document เอง) โดยส่ง NodeType ที่คุณต้องการและ isDeep: true เพื่อค้นหาทั้งต้นไม้ย่อย ไม่ใช่แค่ลูกโดยตรง.
ฉันจะลบโหนดอย่างปลอดภัยขณะทำการวนลูปได้อย่างไร?
รวบรวมโหนดที่ต้องการลบไว้ในอาร์เรย์ก่อน — ตัวอย่างเช่นด้วย NodeCollection.ToArray() — จากนั้นวนลูปอาร์เรย์นั้นและเรียก Remove() บนแต่ละโหนด แทนการแก้ไขคอลเลกชันที่กำลังทำงานอยู่ขณะ foreach กำลังวนลูป.
โหนด FieldStart, FieldSeparator และ FieldEnd คืออะไร?
พวกเขาเป็นค่า NodeType ที่ทำเครื่องหมายกลไกของฟิลด์ Word ที่ฝังอยู่ในสตรีมรัน: FieldStart และ FieldEnd กำหนดขอบเขตของฟิลด์, และ FieldSeparator แบ่งโค้ดของฟิลด์ออกจากผลลัพธ์ที่แสดงในปัจจุบัน.
ฉันจะประมวลผลทุกย่อหน้า ตาราง และรันในเอกสารได้อย่างไร?
สร้างคลาสย่อยจาก DocumentVisitor, แทนที่ VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, และ VisitRun สำหรับประเภทโหนดที่คุณต้องการ, จากนั้นเรียก Accept(visitor) บน Document (หรือรากต้นไม้ย่อยใด ๆ) เพื่อดำเนินการ.
API Reference สรุป
| คลาส/เมธอด | คำอธิบาย |
|---|---|
Node | คลาสฐานเชิงนามธรรมสำหรับโหนดทุกอันในต้นไม้เอกสาร |
CompositeNode | คลาสฐานสำหรับโหนดที่สามารถบรรจุตัวลูกได้ |
CompositeNode.GetChildNodes(nodeType, isDeep) | ดึงตัวลูกทั้งหมดของประเภทที่ระบุ, โดยอาจทำการค้นหาทั้งต้นไม้ย่อยทั้งหมด |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | เพิ่มโหนดเข้าไปในคอนเทนเนอร์ที่ตำแหน่งท้าย, หรือก่อน/หลังโหนดลูกที่มีอยู่ |
NodeType | Enum ที่ระบุประเภทของโหนด (Paragraph, Table, Run, FieldStart และอื่น ๆ) |
NodeCollection | คอลเลกชันที่เป็นรูปธรรมและเปลี่ยนแปลงได้ของอ็อบเจ็กต์ Node |
Node.Clone(isCloneChildren) | คัดลอกโหนด, สามารถรวมโหนดลูกได้ตามต้องการ |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | เดินผ่านซับทรีตามลำดับเอกสาร |
INodeChangingCallback | อินเทอร์เฟซคอลแบ็คที่ได้รับการแจ้งเมื่อโหนดถูกแทรกหรือถูกลบ |
DocumentVisitor | คลาสฐานสำหรับการเดินทางแบบมีโครงสร้างและการกระจายตามประเภทผ่าน Node.Accept(visitor) |