Як працювати з вузлами в .NET
Цей посібник показує, як обходити, запитувати та змінювати дерево вузлів Word-документа безпосередньо в Aspose.Words FOSS для .NET, використовуючи Node and CompositeNode базові класи, які Document, Section, Paragraph, Table, і всі інші елементи документа побудовані на цьому. Охоплює обмеження пошуку за NodeType, додаванням та видаленням вузлів програмно, реагуванням на структурні зміни та використанням DocumentVisitor для типово-диспатчованого обходу.
Покроковий посібник
Крок 1: Встановіть пакет
Aspose.Words FOSS for .NET ще не публікує пакет NuGet — збудуйте бібліотеку з вихідного коду. Клонуйте https://github.com/aspose-words-foss/Aspose.Words-FOSS-for-.NET.git, потім збірте Aspose.Words.sln у конфігурації Release за допомогою SDK .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 та перемикають текст у потоці виконання), передайте конкретне значення 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), що відбулася.
Поширені проблеми та виправлення
Приведення вузла до CompositeNode викликає помилку недійсного приведення типу Вузол є типом листка – наприклад Run or FieldStart – який не може містити дочірні елементи. Перевірте Node.IsComposite спочатку, або використовуйте NodeType щоб підтвердити, що вузол є типом контейнера, перед приведенням типу.
GetChildNodes повертає безпосередні дочірні елементи, коли isDeep має значення false isDeep було залишено false (або пропущено, коли перевантаження за замовчуванням є поверхневим). Передайте isDeep: true to GetChildNodes() для пошуку всього піддерева замість безпосередніх дочірніх елементів.
Модифікація колекції під час її ітерації викликає помилку Вузли були додані або видалені всередині foreach над NodeCollection або результату GetChildNodes. Викличте ToArray() спочатку, щоб матеріалізувати вузли для модифікації, а потім ітеруйте цей масив, змінюючи дерево.
Підклас DocumentVisitor не викликається для деякого вмісту Відвідувач був викликаний через Accept() на вузлі, який фактично не містить цільовий вміст, або відповідний Visit* метод не був перевизначений. Викличте Accept() на предка, який містить вміст, і перевизначте кожен Visit*Start / Visit*End пару, що стосується типів вузлів, що обробляються.
INodeChangingCallback не отримує сповіщення про зміну Зворотний виклик повідомляє про зміни лише на Document якомусь, в якому він був зареєстрований. Переконайтеся, що зворотний виклик прикріплений до правильного Document екземпляра до того, як зміна відбудеться, а не після.
Часті запитання
У чому різниця між 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, перевизначте методи Visit*Start / Visit*End для потрібних типів вузлів, потім викличте Accept(visitor) на Document (або будь-якому корені піддерева), щоб запустити його.