کار با درخت گره‌های سند

کار با درخت گره‌های سند

کار با درخت گره‌های سند

این راهنما نشان می‌دهد که چگونه می‌توان ساختار یک سند را مستقیماً از طریق درخت گره‌های آن مرور و دستکاری کرد — مدل شیئی که 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 را در جریان اجرای (run) مشخص می‌کنند. ارسال مقدار 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) برای اسکن صاف و غیر بازگشتی یک زیردرخت استفاده کنید.
  • به‌جای کدهای ad hoc برای پیمایش درخت، DocumentVisitor را پیاده‌سازی کنید وقتی که همان پردازش باید بر روی انواع گره‌های مختلف اجرا شود.
  • قبل از تبدیل به CompositeNode، Node.IsComposite را بررسی کنید وقتی که با گره‌ای از نوع ناشناخته کار می‌کنید.
  • زمانی که نیاز دارید به اضافه‌ها یا حذف‌های انجام‌شده توسط کدهای دیگر (از جمله عملیات‌های داخلی) واکنش نشان دهید، نه فقط به تغییراتی که کد شما مستقیماً ایجاد می‌کند، یک INodeChangingCallback را ثبت کنید.

مشکلات رایج

مشکلدلیلرفع
تبدیل Node به CompositeNode باعث بروز خطای invalid-cast می‌شودگره یک نوع برگ است (مثلاً 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) گسترش می‌دهد؛ عناصر برگ (مانند Run) مستقیماً از Node ارث می‌برند و نمی‌توانند فرزند داشته باشند.

چگونه می‌توانم تمام گره‌های یک نوع خاص را در یک سند پیدا کنم؟

متد CompositeNode.GetChildNodes(nodeType, isDeep) را بر روی گره‌ای که جستجوی شما را ریشه می‌دهد (اغلب خود Document) فراخوانی کنید، NodeType مورد نظر را پاس دهید و isDeep: true را برای جستجوی کل زیردرخت، نه فقط فرزندان مستقیم، استفاده کنید.

چگونه می‌توانم گره‌ها را هنگام تکرار به‌صورت ایمن حذف کنم؟

ابتدا گره‌های مورد نظر برای حذف را در یک آرایه جمع‌آوری کنید — برای مثال با NodeCollection.ToArray() — سپس بر روی آن آرایه تکرار کنید و Remove() را بر هر گره فراخوانی کنید، به جای این که هنگام تکرار foreach، مجموعه زنده را تغییر دهید.

گره‌های FieldStart، FieldSeparator و FieldEnd چیستند؟

آنها مقادیر NodeType هستند که مکانیزم یک فیلد Word را که در جریان اجرا (run stream) جاسازی شده علامت‌گذاری می‌کنند: 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 objects
Node.Clone(isCloneChildren)یک گره را کپی کنید، به‌اختیار شامل فرزندان آن
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode)در یک زیردرخت به ترتیب سند پیش بروید
INodeChangingCallbackرابط Callback که هنگام درج یا حذف گره‌ها اطلاع داده می‌شود
DocumentVisitorکلاس پایه برای پیمایش ساختاریافته و بر پایه نوع از طریق Node.Accept(visitor)

همچنین ببینید:

 فارسی