Trabalhando com a Árvore de Nós do Documento

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.Any com GetChildNodes(NodeType.Any, true) quando precisar de todos os descendentes independentemente do tipo, em vez de chamar GetChildNodes uma 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 DocumentVisitor em 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.IsComposite antes de fazer cast para CompositeNode ao trabalhar com um nó de tipo desconhecido.
  • Registre um INodeChangingCallback quando 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

ProblemaCausaCorreção
Fazer cast de um Node para CompositeNode gera um erro de invalid-castO nó é um tipo folha (por exemplo Run ou FieldStart) que não pode conter filhosVerifique Node.IsComposite primeiro, ou use NodeType para confirmar que o nó é um tipo contêiner antes de fazer o cast
GetChildNodes só retorna filhos diretosisDeep 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 erroNós foram adicionados ou removidos dentro de um foreach sobre NodeCollection ou o resultado de GetChildNodesMaterialize 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údoO 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 sobrescritoChame 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étodoDescrição
NodeClasse base abstrata para cada nó em uma árvore de documentos
CompositeNodeClasse 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
NodeTypeEnum que identifica o tipo de um nó (Paragraph, Table, Run, FieldStart e assim por diante)
NodeCollectionColeçã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
INodeChangingCallbackInterface de callback notificada quando nós são inseridos ou removidos
DocumentVisitorClasse base para travessia estruturada, despachada por tipo, via Node.Accept(visitor)

Ver também

 Português