Lavorare con l'albero dei nodi del documento
Lavorare con l’albero dei nodi del documento
Questa guida mostra come navigare e manipolare la struttura di un documento direttamente attraverso il suo albero dei nodi — il modello a oggetti su cui sono costruiti Document, Section, Paragraph, Run, Table e ogni altro elemento del documento. Ogni elemento in un documento deriva da Node, e ogni elemento capace di contenere figli deriva da CompositeNode, quindi comprendere queste due classi base spiega come lavorare con contenuti che nessun helper dedicato API copre già.
Le classi base Node e CompositeNode
Node è la classe base astratta per ogni oggetto in un albero di documento. Espone NodeType (un valore enum che identifica il tipo di nodo), ParentNode (il CompositeNode contenitore), Document (il documento proprietario), e PreviousSibling/NextSibling per spostarsi tra nodi allo stesso livello. Node.IsComposite indica se un dato nodo può contenere figli. CompositeNode estende Node per gli elementi contenitori — FirstChild, LastChild e Count descrivono i suoi figli immediati, e HasChildNodes segnala se ne esistono. CompositeNode.GetChildNodes(nodeType, isDeep) recupera tutti i figli di un dato NodeType, opzionalmente cercando l’intero sottoalbero (isDeep) invece solo i figli diretti, e CompositeNode.GetChild(nodeType, index, isDeep) recupera uno singolo per indice.
L’Enum NodeType
NodeType elenca ogni tipo di nodo definito dal modello di documento — tipi strutturali come Document, Section, Body, Paragraph, Table, Row e Cell; tipi di contenuto inline come Run, Shape e Comment; e tipi di meccanismo di campo come FieldStart, FieldSeparator e FieldEnd, che contraddistinguono i confini e il testo di commutazione di un campo Word all’interno del flusso di run. Passare un valore NodeType a GetChildNodes o GetChild limita la ricerca ai nodi di esattamente quel tipo, oppure passare NodeType.Any per corrispondere a tutti i tipi di nodo.
Attraversare e modificare l’albero
CompositeNode.GetEnumerator() ti consente di iterare sui figli diretti di un nodo, e CompositeNode implementa anche SelectNodes(xpath) e SelectSingleNode(xpath) per query basate su XPath sull’intero sottoalbero. Per modificare l’albero, AppendChild(newChild) e PrependChild(newChild) aggiungono un nodo all’inizio o alla fine dei figli, InsertBefore(newChild, refChild) e InsertAfter(newChild, refChild) inseriscono rispetto a un figlio esistente, e RemoveChild(oldChild) o RemoveAllChildren() rimuovono i nodi. CompositeNode.IndexOf(child) restituisce la posizione di un figlio. Su Node stesso, Clone(isCloneChildren) crea una copia — opzionalmente profonda — e Remove() stacca il nodo dal suo genitore. Node.NextPreOrder(rootNode) e PreviousPreOrder(rootNode) percorrono l’albero in ordine documentale, limitati a un nodo radice specificato, il che è un modo comune per scandire un intero sottoalbero nodo per nodo senza codice ricorsivo.
Collezioni di Nodi e Notifiche di Modifica
NodeCollection è una collezione concreta e mutabile di nodi che supporta Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) e ToArray(), insieme a una proprietà Count. NodeList e NodeEnumerator supportano l’iterazione in stile lettura sui risultati di una query, come i nodi restituiti da SelectNodes. Per osservare le modifiche strutturali man mano che avvengono, implementa INodeChangingCallback — i suoi metodi NodeInserting(args), NodeInserted(args), NodeRemoving(args) e NodeRemoved(args) ricevono ciascuno un NodeChangingArgs che descrive il Node interessato, il suo OldParent/NewParent, e il NodeChangingAction che si è verificato.
Visita dei Nodi con DocumentVisitor
Per elaborare ogni tipo di nodo in modo strutturato senza percorrere manualmente l’albero, crea una sottoclasse di DocumentVisitor e sovrascrivi i metodi Visit*Start/Visit*End per i tipi di nodo di tuo interesse — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun, e così via — quindi chiama Node.Accept(visitor) sulla radice del sottoalbero per eseguirlo. Questo è il modello standard per esportare, trasformare o ispezionare un documento senza scrivere un’attraversata ricorsiva personalizzata per ogni caso d’uso.
Suggerimenti e Buone Pratiche
- Usa
NodeType.AnyconGetChildNodes(NodeType.Any, true)quando ti servono tutti i discendenti indipendentemente dal tipo, anziché chiamareGetChildNodesuna volta per tipo. - Preferisci
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)per una scansione piatta e non ricorsiva di un sottoalbero invece di scrivere una discesa ricorsiva manuale. - Implementa
DocumentVisitoranziché codice ad hoc di attraversamento dell’albero quando la stessa elaborazione deve essere eseguita su molti tipi di nodo diversi. - Verifica
Node.IsCompositeprima di effettuare il cast aCompositeNodequando lavori con un nodo di tipo sconosciuto. - Registra un
INodeChangingCallbackquando devi reagire a inserimenti o rimozioni effettuati da altro codice (incluse le operazioni integrate), non solo alle modifiche che il tuo codice apporta direttamente.
Problemi comuni
| Problema | Causa | Correzione |
|---|---|---|
Il cast di un Node a CompositeNode genera un errore di cast non valido | Il nodo è di tipo foglia (ad esempio Run o FieldStart) che non può contenere figli | Controlla prima Node.IsComposite, oppure usa NodeType per confermare che il nodo sia di tipo contenitore prima di eseguire il cast |
GetChildNodes restituisce solo i figli diretti | isDeep è stato lasciato false (o omesso dove il sovraccarico predefinisce shallow) | Passa isDeep: true per cercare l’intero sottoalbero |
| Modificare una collezione mentre la si itera genera un errore | I nodi sono stati aggiunti o rimossi all’interno di un foreach su NodeCollection o dal risultato di GetChildNodes | Materializza prima i nodi da modificare con ToArray(), poi itera sull’array mentre muta l’albero |
Una sottoclasse di DocumentVisitor non viene chiamata per alcuni contenuti | Il visitatore è stato invocato tramite Accept() su un nodo che non contiene il contenuto target, oppure il metodo Visit* corrispondente non è stato sovrascritto | Chiama Accept() su un antenato che contiene il contenuto e sovrascrivi ogni coppia Visit*Start/Visit*End pertinente ai tipi di nodo in elaborazione |
FAQ
Qual è la differenza tra Node e CompositeNode?
Node è la classe base da cui derivano tutti gli elementi del documento. CompositeNode estende Node per gli elementi che possono contenere figli (come Body, Paragraph e Table); gli elementi foglia (come Run) derivano direttamente da Node e non possono contenere figli.
Come posso trovare tutti i nodi di un tipo specifico in un documento?
Chiama CompositeNode.GetChildNodes(nodeType, isDeep) sul nodo che avvia la tua ricerca (spesso il Document stesso), passando il NodeType desiderato e isDeep: true per cercare l’intero sottoalbero, non solo i figli diretti.
Come rimuovere in modo sicuro i nodi durante l’iterazione?
Raccogli prima i nodi da rimuovere in un array — ad esempio con NodeCollection.ToArray() — poi itera su quell’array e chiama Remove() su ogni nodo, invece di modificare la collezione attiva mentre un foreach la sta iterando.
Cosa sono i nodi FieldStart, FieldSeparator e FieldEnd?
Sono valori NodeType che contrassegnano il meccanismo di un campo Word incorporato nel flusso di esecuzione: FieldStart e FieldEnd delimitano il campo, e FieldSeparator separa il codice del campo dal suo risultato attualmente visualizzato.
Come posso elaborare ogni paragrafo, tabella e run in un documento?
Crea una sottoclasse di DocumentVisitor, sovrascrivi VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd e VisitRun per i tipi di nodo di cui hai bisogno, quindi chiama Accept(visitor) sul Document (o su qualsiasi radice di sottoalbero) per eseguirlo.
API Reference Sommario
| Classe/Metodo | Descrizione |
|---|---|
Node | Classe base astratta per ogni nodo in un albero del documento |
CompositeNode | Classe base per i nodi che possono contenere figli |
CompositeNode.GetChildNodes(nodeType, isDeep) | Recupera tutti i figli di un determinato tipo, opzionalmente nell’intero sottoalbero |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Aggiungi un nodo a un contenitore alla fine, o prima/dopo un figlio esistente |
NodeType | Enum che identifica il tipo di un nodo (Paragraph, Table, Run, FieldStart e così via) |
NodeCollection | Collezione concreta e mutabile di oggetti Node |
Node.Clone(isCloneChildren) | Copia un nodo, opzionalmente includendo i suoi figli |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Scorri un sottoalbero in ordine di documento |
INodeChangingCallback | Interfaccia di callback notificata quando i nodi vengono inseriti o rimossi |
DocumentVisitor | Classe base per l’attraversamento strutturato, dispatch basato sul tipo, tramite Node.Accept(visitor) |