כיצד לעבוד עם צמתים ב-.NET
המדריך הזה מראה כיצד לעבור, לבצע שאילתות ולשנות את עץ הצמתים של מסמך Word ישירות ב Aspose.Words FOSS עבור .NET, באמצעות Node and CompositeNode מחלקות הבסיס ש Document, Section, Paragraph, Table, וכל שאר רכיבי המסמך נבנים עליהם. הוא מכסה הגדרת תחום חיפוש על ידי NodeType, הוספה והסרה של צמתים באופן תכנותי, תגובה לשינויים מבניים, ושימוש DocumentVisitor לניווט המופעל לפי סוג.
מדריך שלב-בשלב
שלב 1: התקנת החבילה
Aspose.Words FOSS עבור .NET עדיין לא מפרסם חבילה של NuGet – במקום זאת בנו את הספרייה מהמקור. שכפלו את https://github.com/aspose-words-foss/Aspose.Words-FOSS-for-.NET.git, ולאחר מכן בנו את Aspose.Words.sln בתצורת Release עם ערכת הפיתוח .NET והפנו אל הרכיב Aspose.Words שנוצר מהפרויקט שלכם. הספרייה ממוקדת ל-.NET Standard 2.0, ולכן היא פועלת על .NET Framework 4.6.2+ ו-.NET 6/8/10.
שלב 2: ייבוא המחלקות הדרושות
הוסף הנחיית using Aspose.Words; – Node, CompositeNode, NodeType, NodeCollection, NodeList, INodeChangingCallback ו-DocumentVisitor כולם נמצאים במרחב השמות Aspose.Words לצד Document.
שלב 3: להבין את הצומת ואת מחלקות הבסיס של CompositeNode
כל אובייקט בעץ המסמך יורש מהמחלקה האבסטרקטית Node, שחשופה NodeType (המזהה את סוג הצומת), ParentNode (הCompositeNode המקיף), Document (המסמך הבעלים), ו-PreviousSibling / NextSibling לניווט בין צמתים באותו רמה. בדוק את Node.IsComposite כדי לגלות האם צומת נתון יכול להכיל צאצאים לפני ההמרה שלו. CompositeNode מרחיב את Node עבור אלמנטים קונטיינר – FirstChild, LastChild, ו-Count מתארים את הצאצאים המיידיים שלו, ו-HasChildNodes מדווח האם קיימים כאלה.
שלב 4: שאילת צאצאים לפי סוג
קרא ל-CompositeNode.GetChildNodes(nodeType, isDeep) כדי לקבל כל צאצא של NodeType נתון, תוך העברת isDeep: true לחיפוש בכל תת-העץ במקום רק בצאצאים הישירים, או NodeType.Any כדי להתאים לכל סוג צומת ללא תלות בסוגו. CompositeNode.GetChild(nodeType, index, isDeep) מאחזר צאצא תואם יחיד לפי אינדקס. עבור סוגי צמתים מבניים (Document, Section, Body, Paragraph, Table, Row, Cell), סוגי תוכן משולב (Run, Shape, Comment), וסוגי מכונת שדות (FieldStart, FieldSeparator, FieldEnd, המסמנים את גבולות השדה של Word וטקסט המחליף ב-stream הריצה), העבר את ערך ה-NodeType הספציפי שמזהה את מה שאתה מחפש.
שלב 5: להריץ שאילתות XPath על פני תת-עץ
CompositeNode מיישם גם את SelectNodes(xpath) ו-SelectSingleNode(xpath) לשאילתות מבוססות XPath על פני תת-העץ, ומחזיר תוצאות כ־NodeList (או Node בודד). CompositeNode.GetEnumerator() תומך ב-foreach פשוט על הצאצאים הישירים של צומת כאשר שאילתת XPath יותר מהנדרש.
שלב 6: הוספה, העברה והסרה של צמתים
כדי לשנות את העץ, קרא ל-AppendChild(newChild) או ל-PrependChild(newChild) כדי להוסיף צומת בתחילת או בסוף צאצאי הקונטיינר, ו-InsertBefore(newChild, refChild) או ל-InsertAfter(newChild, refChild) כדי להכניס יחסית לצאצא קיים. RemoveChild(oldChild) מסיר צאצא אחד, ו-RemoveAllChildren() מוחק את כל הצאצאים בבת אחת; CompositeNode.IndexOf(child) מדווח על המיקום של צאצא. על Node עצמו, Clone(isCloneChildren) מעתיק צומת — באופן רשות כולל את צאצאיו — ו-Remove() מנתק את הצומת מההורה שלו.
שלב 7: הפיכת צמתים למוחשיים לפני שינוי של אוסף חי
שינוי של אוסף בזמן שלוליית foreach עוברת עליו – בין אם מדובר ב-NodeCollection או בתוצאה של GetChildNodes – גורם לשגיאה. יש לקרוא ל-ToArray() על האוסף תחילה כדי להמחיז את הצמתים למערך קבוע, ואז לעבור על המערך תוך קריאה ל-Remove() או ביצוע שינויים אחרים בעץ.
שלב 8: סריקה של תת-עץ בסדר המסמך
Node.NextPreOrder(rootNode) ו-PreviousPreOrder(rootNode) עוברים על העץ בסדר המסמך, מוגבלים בצומת שורש נתון – דרך נפוצה לסרוק תת-עץ שלם צומת-אחר-צומת ללא כתיבת קוד מעבר רקורסיבי.
שלב 9: לעבד כל סוג צומת עם DocumentVisitor
לצורך עיבוד של סוגי צמתים שונים בצורה מובנית, יש ליצור תת-מחלקה של המחלקה האבסטרקטית DocumentVisitor ולדרוס את השיטות Visit*Start / Visit*End עבור סוגי הצמתים החשובים – לדוגמה VisitParagraphStart / VisitParagraphEnd, VisitSectionStart / VisitSectionEnd, VisitBodyStart / VisitBodyEnd – ואז לקרוא ל-Node.Accept(visitor) על שורש תת-העץ כדי להריץ אותו. זהו הדפוס הסטנדרטי לייצוא, שינוי, או חקירת מסמך ללא כתיבת מעבר רקורסיבי מותאם לכל מקרה שימוש.
שלב 10: להגיב לשינויים מבניים שנגרמים על ידי קוד אחר
מיישמים INodeChangingCallback כאשר קוד צריך להגיב להוספות או הסרות שנעשו על ידי קוד אחר – כולל פעולות מובנות – ולא רק לשינויים שבוצעו ישירות. השיטות NodeInserting(args), NodeInserted(args), NodeRemoving(args), ו-NodeRemoved(args) שלו מקבלות כל אחת NodeChangingArgs המתארת את ה-Node המושפעת, את ההורה הישן והחדש שלה, ואת ה-NodeChangingAction (Insert או Remove) שהתרחשה.
בעיות נפוצות ותיקונים
המרת Node ל-CompositeNode גוררת שגיאת cast בלתי תקפה הצומת הוא מסוג עלה – לדוגמה Run or FieldStart – שלא יכול להכיל ילדים. בדוק Node.IsComposite קודם, או השתמש ב NodeType כדי לאשר שהצומת הוא מסוג מכולה לפני המרה.
GetChildNodes מחזיר ילדים ישירים כאשר isDeep הוא false isDeep הושאר false (או מושמט כאשר העומס המוגזם ברירת המחדל הוא shallow). העבר isDeep: true to GetChildNodes() לחפש את כל תת-העץ במקום הילדים הישירים.
שינוי של collection בזמן איטרציה עליו גורם לשגיאה Nodes נוספו או הוסרו בתוך foreach על NodeCollection או של GetChildNodes. קרא ToArray() קודם כל לממש את ה-nodes לשינוי, ואז לעבור על המערך הזה תוך שינוי העץ.
מחלקה משנית של DocumentVisitor לא נקראת עבור חלק מהתוכן ה-visitor הופעל באמצעות Accept() ב-node שלא בפועל מכילה את תוכן היעד, או המתאימה Visit* ה-method לא נדרסה. קרא Accept() ב-ancestor שמכיל את התוכן, והדרס כל Visit*Start / Visit*End זוג הרלוונטי לסוגי ה-node המעובדים.
INodeChangingCallback לא מקבל הודעה על שינוי ה-callback מדווח רק על שינויים ב- Document שאליו נרשם. ודא שה-callback מצורף ל- Document instance לפני שהשינוי מתרחש, ולא אחרי.
שאלות נפוצות
מה ההבדל בין Node ל-CompositeNode?
Node היא המחלקה הבסיסית שכל רכיב מסמך יורש ממנה. CompositeNode מרחיבה את Node עבור רכיבים שיכולים להכיל ילדים (כגון Body, Paragraph, ו-Table); רכיבי עלה כגון Run יורשים ישירות מ-Node ואינם יכולים להכיל ילדים.
איך אוכל למצוא את כל ה-nodes מסוג ספציפי במסמך?
קרא ל-CompositeNode.GetChildNodes(nodeType, isDeep) על ה-node שמקשר את החיפוש – לעיתים קרובות ה-Document עצמו – תוך העברת ה-NodeType וה-isDeep: true הרצויים כדי לחפש בכל תת-העץ, ולא רק בילדים הישירים.
איך להסיר nodes בצורה בטוחה בזמן איטרציה?
ראשית, אסוף את ה-nodes שברצונך להסיר למערך – לדוגמה עם NodeCollection.ToArray() – ולאחר מכן עובר על המערך וקורא ל-Remove() על כל node, במקום לשנות את האוסף החי בזמן ש-foreach מבצע איטרציה עליו.
מהם ה-nodes FieldStart, FieldSeparator ו-FieldEnd?
הן ערכי NodeType המסמנים את המנגנון של שדה Word המוטמע ב-run stream: FieldStart ו-FieldEnd קשורים לשדה, ו-FieldSeparator מפריד בין קוד השדה לתוצאה המוצגת כעת.
איך אני מעבד כל פסקה, טבלה וריצה במסמך?
צור מחלקה משנה של DocumentVisitor, עקוף את השיטות Visit*Start / Visit*End עבור סוגי הצמתים הדרושים, ואז קרא ל-Accept(visitor) על ה-Document (או על כל שורש תת-עץ) כדי להפעיל אותו.