Práce se stromem uzlů dokumentu

Práce se stromem uzlů dokumentu

Práce se stromem uzlů dokumentu

Tento průvodce ukazuje, jak navigovat a manipulovat se strukturou dokumentu přímo prostřednictvím jeho stromu uzlů – objektovým modelem, na kterém jsou postaveny Document, Section, Paragraph, Run, Table a všechny ostatní prvky dokumentu. Každý prvek v dokumentu je odvozen od Node a každý prvek, který může obsahovat podřízené, je odvozen od CompositeNode, takže pochopení těchto dvou základních tříd vysvětluje, jak pracovat s obsahem, který žádný dedikovaný pomocný API ještě nepokrývá.


Základní třídy Node a CompositeNode

Node je abstraktní základní třída pro každý objekt ve stromu dokumentu. Poskytuje NodeType (enum hodnota identifikující typ tohoto uzlu), ParentNode (obalující CompositeNode), Document (vlastnický dokument) a PreviousSibling/NextSibling pro pohyb mezi uzly na stejné úrovni. Node.IsComposite uvádí, zda může daný uzel obsahovat podřízené. CompositeNode rozšiřuje Node pro kontejnerové prvky – FirstChild, LastChild a Count popisují jeho okamžité podřízené a HasChildNodes uvádí, zda nějaké existují. CompositeNode.GetChildNodes(nodeType, isDeep) načte všechny podřízené daného NodeType, volitelně prohledává celý podstrom (isDeep) místo pouze přímých podřízených, a CompositeNode.GetChild(nodeType, index, isDeep) načte jeden konkrétní podle indexu.

Výčet NodeType

NodeType vyjmenovává každý typ uzlu, který dokumentový model definuje — strukturální typy jako Document, Section, Body, Paragraph, Table, Row a Cell; typy inline obsahu jako Run, Shape a Comment; a typy pole-mechaniky jako FieldStart, FieldSeparator a FieldEnd, které označují hranice a přepínací text pole Wordu v rámci proudu běhů. Předání hodnoty NodeType do GetChildNodes nebo GetChild omezuje vyhledávání na uzly přesně tohoto typu, nebo předáním NodeType.Any se shodují všechny typy uzlů.

Procházení a úprava stromu

CompositeNode.GetEnumerator() vám umožňuje iterovat přes přímé potomky uzlu a CompositeNode také implementuje SelectNodes(xpath) a SelectSingleNode(xpath) pro dotazy založené na XPath napříč podstromem. Pro úpravu stromu AppendChild(newChild) a PrependChild(newChild) přidávají uzel na začátek nebo konec seznamu potomků, InsertBefore(newChild, refChild) a InsertAfter(newChild, refChild) vkládají relativně k existujícímu potomkovi a RemoveChild(oldChild) nebo RemoveAllChildren() odstraňují uzly. CompositeNode.IndexOf(child) vrací pozici potomka. Na Node samotném Clone(isCloneChildren) vytvoří kopii—volitelně hlubokou—a Remove() odpojí uzel od jeho rodiče. Node.NextPreOrder(rootNode) a PreviousPreOrder(rootNode) procházejí strom v pořadí dokumentu, omezené daným kořenovým uzlem, což je běžný způsob, jak prohledat celý podstrom uzel po uzlu bez rekurzivního kódu.

Kolekce uzlů a oznámení o změnách

NodeCollection je konkrétní, měnitelná kolekce uzlů podporující Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) a ToArray(), spolu s vlastností Count. NodeList a NodeEnumerator podporují iteraci typu read nad výsledky dotazů, například uzly vrácené SelectNodes. Pro sledování strukturálních změn v reálném čase implementujte INodeChangingCallback—jeho metody NodeInserting(args), NodeInserted(args), NodeRemoving(args) a NodeRemoved(args) každá přijímá NodeChangingArgs popisující postižený Node, jeho OldParent/NewParent a NodeChangingAction, ke kterému došlo.

Navštěvování uzlů pomocí DocumentVisitor

Pro zpracování každého typu uzlu strukturovaným způsobem, aniž byste museli ručně procházet strom, vytvořte podtřídu DocumentVisitor a přepište metody Visit*Start/Visit*End pro typy uzlů, o které vám jde—VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun a tak dále—a poté zavolejte Node.Accept(visitor) na kořen podstromu, aby se spustilo. Toto je standardní vzor pro export, transformaci nebo inspekci dokumentu, aniž byste museli psát vlastní rekurzivní procházení pro každý případ použití.


Tipy a osvědčené postupy

  • Použijte NodeType.Any s GetChildNodes(NodeType.Any, true), když potřebujete všechny potomky bez ohledu na typ, místo volání GetChildNodes jednou pro každý typ.
  • Upřednostněte Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) pro ploché, nerekurzivní skenování podstromu místo psaní ručního rekurzivního sestupu.
  • Implementujte DocumentVisitor místo ad hoc kódu pro procházení stromu, když je potřeba stejný proces aplikovat na mnoho různých typů uzlů.
  • Zkontrolujte Node.IsComposite před převodem na CompositeNode, když pracujete s uzlem neznámého typu.
  • Zaregistrujte INodeChangingCallback, když potřebujete reagovat na vkládání nebo odstraňování provedené jiným kódem (včetně vestavěných operací), nikoli jen na změny, které váš vlastní kód provádí přímo.

Časté problémy

ProblémPříčinaOprava
Přetypování Node na CompositeNode vyvolá chybu neplatného přetypováníUzel je typu list (například Run nebo FieldStart), který nemůže obsahovat poduzlyNejprve zkontrolujte Node.IsComposite, nebo použijte NodeType k potvrzení, že uzel je typu kontejneru, před přetypováním
GetChildNodes vrací pouze přímé poduzlyisDeep byl ponechán false (nebo vynechán, kde přetížení výchozí na mělký)Předávejte isDeep: true pro prohledání celého podstromu
Úprava kolekce během její iterace vyvolá chybuUzly byly přidány nebo odebrány uvnitř foreach nad NodeCollection nebo výsledku GetChildNodesNejprve materializujte uzly k úpravě pomocí ToArray(), pak iterujte přes pole při mutaci stromu
Podtřída DocumentVisitor není pro určitý obsah volánaNávštěvník byl vyvolán pomocí Accept() na uzlu, který neobsahuje cílový obsah, nebo nebyla přepsána odpovídající metoda Visit*Zavolejte Accept() na předka, který obsahuje obsah, a přepište každý pár Visit*Start/Visit*End relevantní pro typy uzlů, které se zpracovávají

FAQ

Jaký je rozdíl mezi Node a CompositeNode?

Node je základní třída, ze které dědí každý prvek dokumentu. CompositeNode rozšiřuje Node pro prvky, které mohou obsahovat potomky (například Body, Paragraph a Table); listové prvky (například Run) dědí přímo z Node a nemohou mít potomky.

Jak najdu všechny uzly konkrétního typu v dokumentu?

Zavolejte CompositeNode.GetChildNodes(nodeType, isDeep) na uzlu, který je kořenem vašeho vyhledávání (často samotný Document), předáním požadovaného NodeType a isDeep: true pro prohledání celého podstromu, nikoli jen přímých potomků.

Jak bezpečně odstranit uzly během iterace?

Nejdříve shromážděte uzly k odstranění do pole — například pomocí NodeCollection.ToArray() — pak projděte toto pole a zavolejte Remove() na každý uzel, místo aby se měnila živá kolekce během iterace foreach.

Co jsou uzly FieldStart, FieldSeparator a FieldEnd?

Jedná se o hodnoty NodeType označující mechaniku pole Word vloženého do proudu běhů: FieldStart a FieldEnd svazují pole a FieldSeparator odděluje kód pole od jeho aktuálně zobrazovaného výsledku.

Jak zpracovat každý odstavec, tabulku a run v dokumentu?

Vytvořte podtřídu DocumentVisitor, přepište VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd a VisitRun pro typy uzlů, které potřebujete, a poté zavolejte Accept(visitor) na Document (nebo na libovolný kořen podstromu), aby se spustilo.


API Reference Shrnutí

Třída/MetodaPopis:
NodeAbstraktní základní třída pro každý uzel ve stromu dokumentu
CompositeNodeZákladní třída pro uzly, které mohou obsahovat podřízené
CompositeNode.GetChildNodes(nodeType, isDeep)Získat všechny potomky daného typu, volitelně v celém podstromu
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild)Přidat uzel do kontejneru na konci nebo před/za existujícím potomkem
NodeTypeVýčet (enum) identifikující typ uzlu (Paragraph, Table, Run, FieldStart a tak dále)
NodeCollectionKonkrétní, měnitelná kolekce objektů Node
Node.Clone(isCloneChildren)Zkopírovat uzel, volitelně včetně jeho potomků
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode)Procházet podstrom v pořadí dokumentu
INodeChangingCallbackRozhraní zpětného volání, které je upozorněno, když jsou uzly vloženy nebo odebrány
DocumentVisitorZákladní třída pro strukturovaný, typově dispatchovaný průchod pomocí Node.Accept(visitor)

Viz také:

 Čeština