עבודה עם עץ הצמתים של המסמך
עבודה עם עץ הצמתים של המסמך
מדריך זה מציג כיצד לנווט ולשנות את מבנה המסמך ישירות דרך עץ הצמתים שלו — מודל האובייקטים שעליו נבנים 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 נתון, עם אפשרות לחיפוש בתת-העץ המלא (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 על פני תת-העץ. כדי לשנות את העץ, AppendChild(newChild) ו-PrependChild(newChild) מוסיפים צומת בתחילת או בסוף הילדים, InsertBefore(newChild, refChild) ו-InsertAfter(newChild, refChild) מוסיפים במיקום יחסי לצומת קיים, ו-RemoveChild(oldChild) או RemoveAllChildren() מסירים צמתים. CompositeNode.IndexOf(child) מחזיר את המיקום של הילד. על Node עצמו, Clone(isCloneChildren) יוצר עותק — אופציונלי עמוק — ו-Remove() מפריד את הצומת מההורה שלו. Node.NextPreOrder(rootNode) ו-PreviousPreOrder(rootNode) עוברים על העץ בסדר המסמך, מוגבלים לצומת שורש נתון, שהיא דרך נפוצה לסרוק תת-עץ שלם צומת-בצומת ללא קוד רקורסיבי.
אוספי צמתים והודעות שינוי
NodeCollection הוא אוסף קונקרטי, ניתן לשינוי של צמתים התומך ב-Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) ו-ToArray(), לצד תכונה Count. NodeList ו-NodeEnumerator תומכים באיטרציה בסגנון קריאה על תוצאות שאילתה, כגון הצמתים שהוחזרו על ידי SelectNodes. כדי לצפות בשינויים מבניים בזמן שהם מתרחשים, יש לממש את INodeChangingCallback — השיטות שלו NodeInserting(args), NodeInserted(args), NodeRemoving(args) ו-NodeRemoved(args) מקבלות כל אחת NodeChangingArgs המתאר את ה-Node המושפע, את ה-OldParent/NewParent שלו, ואת ה-NodeChangingAction שהתרחש.
ביקור בצמתים עם DocumentVisitor
כדי לעבד כל סוג צומת בצורה מובנית ללא צורך בהליכה ידנית בעץ, צור מחלקה יורשת מ-DocumentVisitor ושנה את השיטות Visit*Start/Visit*End עבור סוגי הצמתים שמעניינים אותך — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun וכן הלאה — ולאחר מכן קרא ל-Node.Accept(visitor) על שורש תת-העץ כדי להריץ אותו. זוהי תבנית סטנדרטית לייצוא, שינוי או בדיקת מסמך ללא צורך בכתיבת מסלול רקורסיבי מותאם אישית לכל מקרה שימוש.
טיפים ושיטות עבודה מומלצות
- השתמש ב-
NodeType.AnyעםGetChildNodes(NodeType.Any, true)כאשר אתה צריך את כל הצאצאים ללא קשר לסוג, במקום לקרוא ל-GetChildNodesפעם אחת לכל סוג. - העדף את
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)לסריקה שטוחה, לא-רקורסיבית של תת-עץ במקום לכתוב ירידה רקורסיבית ידנית. - ממש
DocumentVisitorבמקום קוד הליכה בעץ אד-הוק כאשר יש צורך להריץ את אותו עיבוד על סוגי צמתים שונים רבים. - בדוק את
Node.IsCompositeלפני המרה ל-CompositeNodeכאשר עובדים עם צומת מסוג לא ידוע. - רשום
INodeChangingCallbackכאשר אתה צריך להגיב להוספות או להסרות שמבוצעות על ידי קוד אחר (כולל פעולות מובנות), ולא רק לשינויים שהקוד שלך יוצר ישירות.
בעיות נפוצות
| בעיה | סיבה | תיקון |
|---|---|---|
המרת Node ל-CompositeNode גורמת לשגיאת המרה לא חוקית | הצומת הוא מסוג עלה (למשל Run או FieldStart) שאינו יכול להכיל צאצאים | בדוק קודם את Node.IsComposite, או השתמש ב-NodeType כדי לאשר שהצומת הוא מסוג מכולה לפני ההמרה |
GetChildNodes מחזיר רק צאצאים ישירים | isDeep נותר false (או הושמט כאשר העומס המוגדר כברירת מחדל הוא רדוד) | העבר isDeep: true כדי לחפש את כל תת-העץ |
| שינוי של אוסף בזמן איטרציה עליו גורם לשגיאה | צמתים נוספו או הוסרו בתוך foreach על NodeCollection או בתוצאה של GetChildNodes | הפוך את הצמתים לשינוי עם ToArray() תחילה, ואז עבור על המערך בזמן שינוי העץ |
תת-מחלקה של DocumentVisitor אינה נקראת עבור חלק מהתוכן | המבקר הופעל באמצעות Accept() על צומת שאינו מכיל את התוכן המבוקש, או שהמתודה המתאימה Visit* לא נדרסה | הפעל Accept() על צאצע שמכיל את התוכן, והחלף כל זוג Visit*Start/Visit*End רלוונטי לסוגי הצמתים שמעובדים |
FAQ
מה ההבדל בין Node ל-CompositeNode?
Node היא המחלקה הבסיסית שממנה נגזרים כל רכיבי המסמך. CompositeNode מרחיבה את Node עבור רכיבים שיכולים להכיל ילדים (כגון Body, Paragraph ו-Table); רכיבי עלה (כגון Run) יורשים ישירות מ-Node ואינם יכולים להכיל ילדים.
איך אני מוצא את כל הצמתים מסוג ספציפי במסמך?
קרא ל-CompositeNode.GetChildNodes(nodeType, isDeep) על הצומת שממנו מתחיל החיפוש שלך (לעיתים ה-Document עצמו), ועביר את ה-NodeType שאתה רוצה ואת isDeep: true כדי לחפש בכל תת-העץ, לא רק בבנים הישירים.
איך אני יכול להסיר צמתים באופן בטוח בזמן איטרציה?
אסוף תחילה את הצמתים שיש להסיר למערך — לדוגמה עם NodeCollection.ToArray() — ואז עבר על המערך וקרא ל-Remove() על כל צומת, במקום לשנות את האוסף החי בזמן ש-foreach מבצע עליו איטרציה.
מהם הצמתים FieldStart, FieldSeparator ו-FieldEnd?
הם ערכי NodeType המסמנים את המנגנון של שדה Word המוטמע במעברי הריצה: FieldStart ו-FieldEnd קובעים את גבולות השדה, ו-FieldSeparator מפריד בין קוד השדה לתוצאה המוצגת כעת.
איך אני מעבד כל פסקה, טבלה ו-run במסמך?
צור מחלקה נגזרת מ-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 | ממשק Callback מודיע כאשר צמתים מוכנסים או מוסרים |
DocumentVisitor | מחלקת בסיס לניווט מובנה, מפולח לפי סוג באמצעות Node.Accept(visitor) |