کار با درخت گرههای سند
کار با درخت گرههای سند
این راهنما نشان میدهد که چگونه میتوان ساختار یک سند را مستقیماً از طریق درخت گرههای آن مرور و دستکاری کرد — مدل شیئی که 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) | یک گره را به یک مخزن در انتها اضافه کنید، یا قبل/بعد از یک فرزند موجود |
NodeType | enum شناساییکننده نوع یک گره (Paragraph، Table، Run، FieldStart، و غیره) |
NodeCollection | مجموعهٔ عینی، قابل تغییر از اشیاء Node objects |
Node.Clone(isCloneChildren) | یک گره را کپی کنید، بهاختیار شامل فرزندان آن |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | در یک زیردرخت به ترتیب سند پیش بروید |
INodeChangingCallback | رابط Callback که هنگام درج یا حذف گرهها اطلاع داده میشود |
DocumentVisitor | کلاس پایه برای پیمایش ساختاریافته و بر پایه نوع از طریق Node.Accept(visitor) |