العمل مع شجرة عقد المستند

العمل مع شجرة عقد المستند

العمل مع شجرة عقد المستند

يُظهر هذا الدليل كيفية التنقل في بنية المستند والتعامل معها مباشرةً عبر شجرة العقد الخاصة به — نموذج الكائنات الذي تُبنى عليه Document، Section، Paragraph، Run، Table، وكل عنصر آخر في المستند. كل عنصر في المستند يُشتق من Node، وكل عنصر قادر على احتواء أبناء يُشتق من CompositeNode، لذا فإن فهم هاتين الفئتين الأساسيتين يوضح كيفية العمل مع المحتوى الذي لا يغطيه أي مساعد مخصص API بالفعل.


فئات الأساس للعقدة و CompositeNode

Node هي الفئة الأساسية المجردة لكل كائن في شجرة المستند. تُظهر NodeType (قيمة تعداد تحدد نوع العقدة)، ParentNode (الـ CompositeNode المحيط)، Document (المستند المالِك)، و PreviousSibling/NextSibling للتنقل بين العقد على نفس المستوى. يُبلغ Node.IsComposite عما إذا كانت العقدة المعطاة يمكنها احتواء أبناء. يُوسّع CompositeNode Node لعناصر الحاوية — FirstChild، LastChild، وCount يصفون أبنائه المباشرين، وHasChildNodes يُبلغ عما إذا كان هناك أي منها. CompositeNode.GetChildNodes(nodeType, isDeep) يسترجع جميع أبناء الـ NodeType المعطى، مع إمكانية البحث في الشجرة الفرعية الكاملة (isDeep) بدلاً من الأبناء المباشرين فقط، وCompositeNode.GetChild(nodeType, index, isDeep) يسترجع واحداً منها بحسب الفهرس.

تعداد 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)إضافة عقدة إلى الحاوية في النهاية، أو قبل/بعد عنصر فرعي موجود
NodeTypeEnum يحدد نوع العقدة (Paragraph، Table، Run، FieldStart، وما إلى ذلك)
NodeCollectionمجموعة صلبة وقابلة للتغيير من كائنات Node
Node.Clone(isCloneChildren)نسخ عقدة، اختياريًا بما في ذلك أبنائها
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode)التنقل عبر شجرة فرعية بترتيب المستند
INodeChangingCallbackواجهة رد النداء التي يتم إبلاغها عندما تُدرج العقد أو تُزال.
DocumentVisitorالفئة الأساسية للتنقل المهيكل والمُرسل حسب النوع عبر Node.Accept(visitor)

انظر أيضاً

 العربية