Arbeta med dokumentnodträdet
Arbeta med dokumentnodträdet
Den här guiden visar hur man navigerar och manipulerar ett dokuments struktur direkt via dess nodträd — objektmodellen som Document, Section, Paragraph, Run, Table och alla andra dokumentelement bygger på. Varje element i ett dokument ärver från Node, och varje element som kan innehålla barn ärver från CompositeNode, så att förstå dessa två basklasser förklarar hur man arbetar med innehåll som ingen dedikerad hjälpfunktion API redan täcker.
Nod- och CompositeNode basklasser
Node är den abstrakta basklassen för varje objekt i ett dokumentträd. Den exponerar NodeType (ett enum-värde som identifierar vilken typ av nod detta är), ParentNode (det omslutande CompositeNode), Document (det ägande dokumentet) och PreviousSibling/NextSibling för att navigera mellan noder på samma nivå. Node.IsComposite rapporterar om en given nod kan innehålla barn. CompositeNode utökar Node för behållarelement — FirstChild, LastChild och Count beskriver dess omedelbara barn, och HasChildNodes rapporterar om några finns. CompositeNode.GetChildNodes(nodeType, isDeep) hämtar alla barn till ett givet NodeType, valfritt sökande i hela underträdet (isDeep) snarare än bara direkta barn, och CompositeNode.GetChild(nodeType, index, isDeep) hämtar ett enskilt efter index.
Enumen NodeType
NodeType listar alla typer av noder som dokumentmodellen definierar — strukturella typer såsom Document, Section, Body, Paragraph, Table, Row och Cell; inline-innehållstyper såsom Run, Shape och Comment; och fält-maskineri-typer såsom FieldStart, FieldSeparator och FieldEnd, vilka markerar gränserna och växlingstexten för ett Word-fält i körningsströmmen. Att skicka ett NodeType-värde till GetChildNodes eller GetChild begränsar sökningen till noder av exakt den typen, eller skicka NodeType.Any för att matcha alla nodtyper.
Traversera och modifiera trädet
CompositeNode.GetEnumerator() låter dig iterera över en nods direkta barn, och CompositeNode implementerar även SelectNodes(xpath) och SelectSingleNode(xpath) för XPath-baserade frågor över underträdet. För att modifiera trädet lägger AppendChild(newChild) och PrependChild(newChild) till en nod i början eller slutet av barnen, InsertBefore(newChild, refChild) och InsertAfter(newChild, refChild) infogar relativt en befintlig barnnod, och RemoveChild(oldChild) eller RemoveAllChildren() tar bort noder. CompositeNode.IndexOf(child) returnerar ett barns position. På Node själv gör Clone(isCloneChildren) en kopia — valfritt djup — och Remove() kopplar loss noden från dess förälder. Node.NextPreOrder(rootNode) och PreviousPreOrder(rootNode) går igenom trädet i dokumentordning, begränsat av en given rotnod, vilket är ett vanligt sätt att skanna ett helt underträd nod-för-nod utan rekursiv kod.
Nodsamlingar och förändringsaviseringar
NodeCollection är en konkret, muterbar samling av noder som stödjer Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) och ToArray(), samt en Count-egenskap. NodeList och NodeEnumerator stödjer läsliknande iteration över frågeresultat, såsom noderna som returneras av SelectNodes. För att observera strukturella förändringar i realtid, implementera INodeChangingCallback — dess NodeInserting(args), NodeInserted(args), NodeRemoving(args) och NodeRemoved(args) metoder får var och en ett NodeChangingArgs som beskriver den påverkade Node, dess OldParent/NewParent och den NodeChangingAction som inträffade.
Besöka noder med DocumentVisitor
För att bearbeta varje nodtyp på ett strukturerat sätt utan att manuellt gå igenom trädet, skapa en subklass av DocumentVisitor och åsidosätt Visit*Start/Visit*End-metoderna för de nodtyper du är intresserad av — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun och så vidare — och anropa sedan Node.Accept(visitor) på rot-noden i underträdet för att köra den. Detta är det vanliga mönstret för att exportera, transformera eller inspektera ett dokument utan att skriva anpassad rekursiv traversering för varje användningsfall.
Tips och bästa praxis
- Använd
NodeType.AnymedGetChildNodes(NodeType.Any, true)när du behöver alla ättlingar oavsett typ, snarare än att anropaGetChildNodesen gång per typ. - Föredra
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)för en platt, icke-rekursiv genomsökning av ett underträd istället för att skriva manuell rekursiv nedstigning. - Implementera
DocumentVisitoristället för ad hoc trädvandringkod när samma bearbetning måste köras mot många olika nodtyper. - Kontrollera
Node.IsCompositeinnan du castar tillCompositeNodenär du arbetar med en nod av okänd typ. - Registrera en
INodeChangingCallbacknär du behöver reagera på insättningar eller borttagningar som görs av annan kod (inklusive inbyggda operationer), inte bara förändringar som din egen kod gör direkt.
Vanliga problem
| Problem | Orsak | Lösning |
|---|---|---|
Att kasta en Node till CompositeNode kastar ett fel för ogiltig typkonvertering | Noden är en bladtyp (till exempel Run eller FieldStart) som inte kan innehålla barn | Kontrollera Node.IsComposite först, eller använd NodeType för att bekräfta att noden är en containertyp innan du kastar |
GetChildNodes returnerar endast direkta barn | isDeep lämnades false (eller utelämnades där överlagringen standardar till ytlig) | Skicka isDeep: true för att söka i hela underträdet |
| Att modifiera en samling medan man itererar över den kastar ett fel | Noder lades till eller togs bort inuti en foreach över NodeCollection eller resultatet av GetChildNodes | Materialisera noderna som ska modifieras med ToArray() först, iterera sedan över arrayen medan trädet muteras |
En DocumentVisitor subklass anropas inte för visst innehåll | Besökaren anropades via Accept() på en nod som inte innehåller mål-innehållet, eller så överskuggades inte den motsvarande Visit*-metoden | Anropa Accept() på en förfader som innehåller innehållet, och överskugga varje Visit*Start/Visit*End-par som är relevant för de nodtyper som bearbetas |
FAQ
Vad är skillnaden mellan Node och CompositeNode?
Node är basklassen som varje dokumentelement ärver från. CompositeNode utökar Node för element som kan innehålla barn (såsom Body, Paragraph och Table); löv-element (såsom Run) ärver direkt från Node och kan inte ha barn.
Hur hittar jag alla noder av en specifik typ i ett dokument?
Anropa CompositeNode.GetChildNodes(nodeType, isDeep) på noden som utgör roten för din sökning (ofta Document själv), och skicka med den NodeType du vill ha samt isDeep: true för att söka i hela underträdet, inte bara direkta barn.
Hur tar jag säkert bort noder medan jag itererar?
Samla först noderna som ska tas bort i en array — till exempel med NodeCollection.ToArray() — och iterera sedan över den arrayen och anropa Remove() på varje nod, istället för att modifiera den levande samlingen medan en foreach itererar den.
Vad är FieldStart, FieldSeparator och FieldEnd noder?
De är NodeType-värden som markerar maskineriet i ett Word-fält inbäddat i körströmmen: FieldStart och FieldEnd binder fältet, och FieldSeparator delar fältets kod från dess för närvarande visade resultat.
Hur bearbetar jag varje stycke, tabell och run i ett dokument?
Skapa en subklass av DocumentVisitor, åsidosätt VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd och VisitRun för de nodtyper du behöver, och anropa sedan Accept(visitor) på Document (eller någon underträdsrot) för att köra den.
API Reference Sammanfattning
| Klass/Metod | Beskrivning |
|---|---|
Node | Abstrakt basklass för varje nod i ett dokumentträd |
CompositeNode | Basklass för noder som kan innehålla barn |
CompositeNode.GetChildNodes(nodeType, isDeep) | Hämta alla barn av en given typ, eventuellt över hela underträdet |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Lägg till en nod i en behållare i slutet, eller före/efter ett befintligt barn |
NodeType | Enum som identifierar typen av en nod (Paragraph, Table, Run, FieldStart och så vidare) |
NodeCollection | Konkret, muterbar samling av Node-objekt |
Node.Clone(isCloneChildren) | Kopiera en nod, eventuellt inklusive dess barn |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Steg igenom ett underträd i dokumentordning |
INodeChangingCallback | Callback-gränssnitt meddelas när noder infogas eller tas bort |
DocumentVisitor | Basklass för strukturerad, typ-styrd traversering via Node.Accept(visitor) |