Trabalhando com a Árvore de Nós do Documento
Trabalhando com a Árvore de Nós do Documento
Este guia mostra como navegar e manipular a estrutura de um documento diretamente através de sua árvore de nós — o modelo de objeto que Document, Section, Paragraph, Run, Table e todos os demais elementos do documento são construídos sobre. Cada elemento em um documento deriva de Node, e cada elemento capaz de conter filhos deriva de CompositeNode, portanto entender essas duas classes base explica como trabalhar com conteúdo que nenhum helper dedicado API já cobre.
As Classes Base Node e CompositeNode
Node é a classe base abstrata para todo objeto em uma árvore de documento. Ela expõe NodeType (um valor enum que identifica que tipo de nó é este), ParentNode (o CompositeNode que o envolve), Document (o documento proprietário) e PreviousSibling/NextSibling para mover-se entre nós no mesmo nível. Node.IsComposite indica se um determinado nó pode conter filhos. CompositeNode estende Node para elementos contêiner — FirstChild, LastChild e Count descrevem seus filhos imediatos, e HasChildNodes indica se existem algum(s). CompositeNode.GetChildNodes(nodeType, isDeep) recupera todos os filhos de um determinado NodeType, opcionalmente pesquisando a subárvore completa (isDeep) em vez de apenas os filhos diretos, e CompositeNode.GetChild(nodeType, index, isDeep) recupera um único por índice.
O Enum NodeType
NodeType enumera todos os tipos de nó que o modelo de documento define — tipos estruturais como Document, Section, Body, Paragraph, Table, Row e Cell; tipos de conteúdo inline como Run, Shape e Comment; e tipos de maquinaria de campo como FieldStart, FieldSeparator e FieldEnd, que marcam os limites e o texto de troca de um campo Word dentro do fluxo de execução. Passar um valor NodeType para GetChildNodes ou GetChild restringe a busca a nós exatamente desse tipo, ou passe NodeType.Any para corresponder a todos os tipos de nó.
Percorrendo e Modificando a Árvore
CompositeNode.GetEnumerator() permite iterar sobre os filhos diretos de um nó, e CompositeNode também implementa SelectNodes(xpath) e SelectSingleNode(xpath) para consultas baseadas em XPath em toda a subárvore. Para modificar a árvore, AppendChild(newChild) e PrependChild(newChild) adicionam um nó no início ou no final dos filhos, InsertBefore(newChild, refChild) e InsertAfter(newChild, refChild) inserem relativo a um filho existente, e RemoveChild(oldChild) ou RemoveAllChildren() removem nós. CompositeNode.IndexOf(child) retorna a posição de um filho. No próprio Node, Clone(isCloneChildren) faz uma cópia — opcionalmente profunda — e Remove() desanexa o nó de seu pai. Node.NextPreOrder(rootNode) e PreviousPreOrder(rootNode) percorrem a árvore em ordem de documento, limitados por um nó raiz fornecido, o que é uma forma comum de varrer toda a subárvore nó por nó sem código recursivo.
Coleções de Nós e Notificações de Alteração
NodeCollection é uma coleção concreta e mutável de nós que suporta Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) e ToArray(), além de uma propriedade Count. NodeList e NodeEnumerator suportam iteração no estilo de leitura sobre resultados de consultas, como os nós retornados por SelectNodes. Para observar mudanças estruturais à medida que ocorrem, implemente INodeChangingCallback — seus métodos NodeInserting(args), NodeInserted(args), NodeRemoving(args) e NodeRemoved(args) recebem cada um um NodeChangingArgs descrevendo o Node afetado, seu OldParent/NewParent, e o NodeChangingAction que ocorreu.
Visitando Nós com DocumentVisitor
Para processar cada tipo de nó de forma estruturada sem percorrer manualmente a árvore, crie uma subclasse de DocumentVisitor e substitua os métodos Visit*Start/Visit*End para os tipos de nó que lhe interessam — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun, e assim por diante — então chame Node.Accept(visitor) na raiz da subárvore para executá-lo. Este é o padrão usual para exportar, transformar ou inspecionar um documento sem escrever uma travessia recursiva personalizada para cada caso de uso.
Dicas e Melhores Práticas
- Use
NodeType.AnycomGetChildNodes(NodeType.Any, true)quando precisar de todos os descendentes independentemente do tipo, em vez de chamarGetChildNodesuma vez por tipo. - Prefira
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)para uma varredura plana e não recursiva de uma subárvore em vez de escrever uma descida recursiva manual. - Implemente
DocumentVisitorem vez de código ad hoc de percorrimento de árvore quando o mesmo processamento precisa ser executado em muitos tipos diferentes de nós. - Verifique
Node.IsCompositeantes de fazer cast paraCompositeNodeao trabalhar com um nó de tipo desconhecido. - Registre um
INodeChangingCallbackquando precisar reagir a inserções ou remoções feitas por outro código (incluindo operações incorporadas), não apenas às alterações que seu próprio código faz diretamente.
Problemas comuns
| Problema | Causa | Correção |
|---|---|---|
Fazer cast de um Node para CompositeNode gera um erro de invalid-cast | O nó é um tipo folha (por exemplo Run ou FieldStart) que não pode conter filhos | Verifique Node.IsComposite primeiro, ou use NodeType para confirmar que o nó é um tipo contêiner antes de fazer o cast |
GetChildNodes só retorna filhos diretos | isDeep foi deixado false (ou omitido onde a sobrecarga padrão é superficial) | Passe isDeep: true para pesquisar toda a subárvore |
| Modificar uma coleção enquanto se itera sobre ela gera um erro | Nós foram adicionados ou removidos dentro de um foreach sobre NodeCollection ou o resultado de GetChildNodes | Materialize os nós a serem modificados com ToArray() primeiro, depois itere sobre o array enquanto altera a árvore |
Uma subclasse de DocumentVisitor não é chamada para algum conteúdo | O visitante foi invocado via Accept() em um nó que não contém o conteúdo alvo, ou o método correspondente Visit* não foi sobrescrito | Chame Accept() em um ancestral que contém o conteúdo, e sobrescreva cada par Visit*Start/Visit*End relevante aos tipos de nó sendo processados |
FAQ
Qual é a diferença entre Node e CompositeNode?
Node é a classe base da qual todo elemento de documento deriva. CompositeNode estende Node para elementos que podem conter filhos (como Body, Paragraph e Table); elementos folha (como Run) derivam diretamente de Node e não podem conter filhos.
Como encontro todos os nós de um tipo específico em um documento?
Chame CompositeNode.GetChildNodes(nodeType, isDeep) no nó que origina sua busca (geralmente o próprio Document), passando o NodeType desejado e isDeep: true para pesquisar toda a subárvore, não apenas os filhos diretos.
Como remover nós com segurança enquanto itero?
Colete os nós a serem removidos em um array primeiro — por exemplo com NodeCollection.ToArray() — depois itere sobre esse array e chame Remove() em cada nó, ao invés de modificar a coleção ao vivo enquanto um foreach a itera.
O que são os nós FieldStart, FieldSeparator e FieldEnd?
Eles são valores NodeType que marcam a mecânica de um campo Word incorporado no fluxo de execução: FieldStart e FieldEnd delimitam o campo, e FieldSeparator separa o código do campo do seu resultado atualmente exibido.
Como processar cada parágrafo, tabela e run em um documento?
Crie uma subclasse de DocumentVisitor, sobrescreva VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd e VisitRun para os tipos de nó que você precisa, então chame Accept(visitor) no Document (ou em qualquer raiz de subárvore) para executá-lo.
API Reference Resumo
| Classe/Método | Descrição |
|---|---|
Node | Classe base abstrata para cada nó em uma árvore de documentos |
CompositeNode | Classe base para nós que podem conter filhos |
CompositeNode.GetChildNodes(nodeType, isDeep) | Recuperar todos os filhos de um determinado tipo, opcionalmente em toda a subárvore |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | Adicionar um nó a um contêiner no final, ou antes/depois de um filho existente |
NodeType | Enum que identifica o tipo de um nó (Paragraph, Table, Run, FieldStart e assim por diante) |
NodeCollection | Coleção concreta e mutável de objetos Node |
Node.Clone(isCloneChildren) | Copiar um nó, opcionalmente incluindo seus filhos |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | Percorrer uma subárvore em ordem de documento |
INodeChangingCallback | Interface de callback notificada quando nós são inseridos ou removidos |
DocumentVisitor | Classe base para travessia estruturada, despachada por tipo, via Node.Accept(visitor) |