Praca z drzewem węzłów dokumentu

Praca z drzewem węzłów dokumentu

Praca z drzewem węzłów dokumentu

Ten przewodnik pokazuje, jak nawigować i manipulować strukturą dokumentu bezpośrednio poprzez jego drzewo węzłów — model obiektowy, na którym opierają się Document, Section, Paragraph, Run, Table oraz każdy inny element dokumentu. Każdy element w dokumencie dziedziczy po Node, a każdy element zdolny do przechowywania dzieci dziedziczy po CompositeNode, więc zrozumienie tych dwóch klas bazowych wyjaśnia, jak pracować z treścią, której nie obejmuje żaden dedykowany pomocnik API.


Klasy bazowe Node i CompositeNode

Node jest abstrakcyjną klasą bazową dla każdego obiektu w drzewie dokumentu. Udostępnia NodeType (wartość wyliczeniowa identyfikująca typ węzła), ParentNode (otaczający CompositeNode), Document (dokument macierzysty) oraz PreviousSibling/NextSibling do przemieszczania się po węzłach na tym samym poziomie. Node.IsComposite informuje, czy dany węzeł może zawierać dzieci. CompositeNode rozszerza Node dla elementów kontenerów — FirstChild, LastChild i Count opisują jego bezpośrednie dzieci, a HasChildNodes informuje, czy jakiekolwiek istnieją. CompositeNode.GetChildNodes(nodeType, isDeep) pobiera wszystkie dzieci danego NodeType, opcjonalnie przeszukując cały poddrzew (isDeep) zamiast tylko bezpośrednich dzieci, a CompositeNode.GetChild(nodeType, index, isDeep) pobiera pojedyncze dziecko według indeksu.

Wyliczenie NodeType

NodeType wymienia każdy rodzaj węzła definiowany przez model dokumentu — typy strukturalne takie jak Document, Section, Body, Paragraph, Table, Row i Cell; typy treści inline takie jak Run, Shape i Comment; oraz typy mechanizmu pól takie jak FieldStart, FieldSeparator i FieldEnd, które oznaczają granice i tekst przełącznika pola Word w strumieniu run. Przekazanie wartości NodeType do GetChildNodes lub GetChild ogranicza wyszukiwanie do węzłów dokładnie tego typu, lub przekazanie NodeType.Any dopasowuje każdy typ węzła.

Przeglądanie i modyfikowanie drzewa

CompositeNode.GetEnumerator() pozwala iterować po bezpośrednich dzieciach węzła, a CompositeNode implementuje również SelectNodes(xpath) i SelectSingleNode(xpath) dla zapytań XPath w całym poddrzewie. Aby modyfikować drzewo, AppendChild(newChild) i PrependChild(newChild) dodają węzeł na początek lub koniec dzieci, InsertBefore(newChild, refChild) i InsertAfter(newChild, refChild) wstawiają względem istniejącego dziecka, a RemoveChild(oldChild) lub RemoveAllChildren() usuwają węzły. CompositeNode.IndexOf(child) zwraca pozycję dziecka. Na samym Node, Clone(isCloneChildren) tworzy kopię — opcjonalnie głęboką — i Remove() odłącza węzeł od jego rodzica. Node.NextPreOrder(rootNode) i PreviousPreOrder(rootNode) przechodzą drzewo w kolejności dokumentu, ograniczone do podanego węzła korzenia, co jest powszechnym sposobem skanowania całego poddrzewa węzeł po węźle bez kodu rekurencyjnego.

Kolekcje węzłów i powiadomienia o zmianach

NodeCollection jest konkretną, mutowalną kolekcją węzłów obsługującą Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) oraz ToArray(), wraz z własnością Count. NodeList i NodeEnumerator wspierają iterację w stylu odczytu nad wynikami zapytań, takimi jak węzły zwrócone przez SelectNodes. Aby obserwować zmiany strukturalne w czasie rzeczywistym, zaimplementuj INodeChangingCallback — jego metody NodeInserting(args), NodeInserted(args), NodeRemoving(args) i NodeRemoved(args) otrzymują NodeChangingArgs opisujący dotknięty Node, jego OldParent/NewParent oraz NodeChangingAction, które wystąpiło.

Odwiedzanie węzłów przy użyciu DocumentVisitor

Aby przetwarzać każdy typ węzła w ustrukturyzowany sposób bez ręcznego przechodzenia drzewa, utwórz podklasę DocumentVisitor i nadpisz metody Visit*Start/Visit*End dla interesujących Cię typów węzłów — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun i tak dalej — a następnie wywołaj Node.Accept(visitor) na korzeniu poddrzewa, aby go uruchomić. Jest to standardowy wzorzec eksportowania, transformacji lub inspekcji dokumentu bez konieczności pisania własnych rekurencyjnych przeglądów dla każdego przypadku użycia.


Wskazówki i najlepsze praktyki

  • Używaj NodeType.Any z GetChildNodes(NodeType.Any, true), gdy potrzebujesz wszystkich potomków niezależnie od typu, zamiast wywoływać GetChildNodes raz na typ.
  • Preferuj Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) do płaskiego, nierrekursywnego skanowania poddrzewa zamiast ręcznego pisania rekurencyjnego zstępowania.
  • Zaimplementuj DocumentVisitor zamiast ad hoc kodu przeglądającego drzewo, gdy to samo przetwarzanie musi być uruchamiane na wielu różnych typach węzłów.
  • Sprawdź Node.IsComposite przed rzutowaniem na CompositeNode, gdy pracujesz z węzłem nieznanego typu.
  • Zarejestruj INodeChangingCallback, gdy musisz reagować na wstawienia lub usunięcia dokonywane przez inny kod (w tym wbudowane operacje), a nie tylko na zmiany wprowadzane bezpośrednio przez Twój własny kod.

Typowe problemy

ProblemPrzyczynaPoprawka
Rzutowanie Node na CompositeNode powoduje błąd nieprawidłowego rzutowaniaWęzeł jest typem liścia (na przykład Run lub FieldStart), który nie może zawierać dzieciNajpierw sprawdź Node.IsComposite, lub użyj NodeType, aby potwierdzić, że węzeł jest typem kontenera przed rzutowaniem
GetChildNodes zwraca tylko bezpośrednie dzieciisDeep został pozostawiony false (lub pominięty, gdy przeciążenie domyślnie jest płytkie)Przekaż isDeep: true, aby przeszukać całe poddrzewo
Modyfikowanie kolekcji podczas iteracji po niej powoduje błądWęzły zostały dodane lub usunięte wewnątrz foreach nad NodeCollection lub w wyniku GetChildNodesNajpierw materializuj węzły do modyfikacji przy użyciu ToArray(), a następnie iteruj po tablicy, mutując drzewo
Podklasa DocumentVisitor nie jest wywoływana dla niektórych treściOdwiedzający został wywołany przez Accept() na węźle, który nie zawiera docelowej treści, lub odpowiadająca metoda Visit* nie została nadpisanaWywołaj Accept() na przodku, który zawiera treść, i nadpisz każdą parę Visit*Start/Visit*End istotną dla przetwarzanych typów węzłów

FAQ

Jaka jest różnica między Node a CompositeNode?

Node jest klasą bazową, z której dziedziczy każdy element dokumentu. CompositeNode rozszerza Node dla elementów, które mogą zawierać dzieci (takie jak Body, Paragraph i Table); elementy liściaste (takie jak Run) dziedziczą bezpośrednio po Node i nie mogą posiadać dzieci.

Jak znaleźć wszystkie węzły określonego typu w dokumencie?

Wywołaj CompositeNode.GetChildNodes(nodeType, isDeep) na węźle, który jest korzeniem twojego wyszukiwania (często sam Document), przekazując NodeType, którego potrzebujesz, oraz isDeep: true, aby przeszukać cały poddrzewo, a nie tylko bezpośrednie dzieci.

Jak bezpiecznie usuwać węzły podczas iteracji?

Zbierz najpierw węzły do usunięcia w tablicę — na przykład przy użyciu NodeCollection.ToArray() —, a następnie iteruj po tej tablicy i wywołaj Remove() na każdym węźle, zamiast modyfikować bieżącą kolekcję, gdy foreach ją iteruje.

Co to są węzły FieldStart, FieldSeparator i FieldEnd?

Są to wartości NodeType oznaczające mechanizm pola Word osadzonego w strumieniu uruchamiania: FieldStart i FieldEnd wiążą pole, a FieldSeparator oddziela kod pola od jego aktualnie wyświetlanego wyniku.

Jak przetworzyć każdy akapit, tabelę i run w dokumencie?

Utwórz podklasę DocumentVisitor, nadpisz VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd oraz VisitRun dla potrzebnych typów węzłów, a następnie wywołaj Accept(visitor) na Document (lub dowolnym korzeniu poddrzewa), aby go uruchomić.


API Reference Podsumowanie

Klasa/MetodaOpis
NodeAbstrakcyjna klasa bazowa dla każdego węzła w drzewie dokumentu
CompositeNodeKlasa bazowa dla węzłów, które mogą zawierać dzieci
CompositeNode.GetChildNodes(nodeType, isDeep)Pobierz wszystkie dzieci określonego typu, opcjonalnie w całym poddrzewie
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild)Dodaj węzeł do kontenera na końcu lub przed/po istniejącym dziecku
NodeTypeWyliczenie identyfikujące rodzaj węzła (Paragraph, Table, Run, FieldStart i tak dalej)
NodeCollectionKonkretna, modyfikowalna kolekcja obiektów Node
Node.Clone(isCloneChildren)Skopiuj węzeł, opcjonalnie łącznie z jego dziećmi
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode)Przejdź przez poddrzewo w kolejności dokumentu
INodeChangingCallbackInterfejs wywołania zwrotnego powiadamia, gdy węzły są wstawiane lub usuwane
DocumentVisitorKlasa bazowa dla strukturalnego, typowo-rozsyłanego przeglądania za pomocą Node.Accept(visitor)

Zobacz także

 Polski