Trabajando con el árbol de nodos del documento

Trabajando con el árbol de nodos del documento

Trabajando con el árbol de nodos del documento

Esta guía muestra cómo navegar y manipular la estructura de un documento directamente a través de su árbol de nodos — el modelo de objetos sobre el que se construyen Document, Section, Paragraph, Run, Table y cualquier otro elemento del documento. Cada elemento en un documento deriva de Node, y cada elemento capaz de contener hijos deriva de CompositeNode, por lo que comprender estas dos clases base explica cómo trabajar con contenido que aún no cubre ningún API de ayuda dedicado.


Las clases base Node y CompositeNode

Node es la clase base abstracta para cada objeto en un árbol de documento. Expone NodeType (un valor enum que identifica qué tipo de nodo es), ParentNode (el CompositeNode contenedor), Document (el documento propietario), y PreviousSibling/NextSibling para moverse entre nodos del mismo nivel. Node.IsComposite indica si un nodo dado puede contener hijos. CompositeNode amplía Node para elementos contenedores — FirstChild, LastChild y Count describen sus hijos inmediatos, y HasChildNodes indica si existen. CompositeNode.GetChildNodes(nodeType, isDeep) recupera todos los hijos de un NodeType dado, opcionalmente buscando en todo el subárbol (isDeep) en lugar de solo los hijos directos, y CompositeNode.GetChild(nodeType, index, isDeep) recupera uno solo por índice.

El enum NodeType

NodeType enumera cada tipo de nodo que define el modelo de documento — tipos estructurales como Document, Section, Body, Paragraph, Table, Row y Cell; tipos de contenido en línea como Run, Shape y Comment; y tipos de maquinaria de campos como FieldStart, FieldSeparator y FieldEnd, que marcan los límites y el texto de cambio de un campo de Word dentro del flujo de ejecución. Pasar un valor NodeType a GetChildNodes o GetChild limita la búsqueda a nodos de exactamente ese tipo, o pase NodeType.Any para coincidir con cualquier tipo de nodo.

Recorriendo y modificando el árbol

CompositeNode.GetEnumerator() te permite iterar sobre los hijos directos de un nodo, y CompositeNode también implementa SelectNodes(xpath) y SelectSingleNode(xpath) para consultas basadas en XPath a lo largo del subárbol. Para modificar el árbol, AppendChild(newChild) y PrependChild(newChild) añaden un nodo al inicio o al final de los hijos, InsertBefore(newChild, refChild) y InsertAfter(newChild, refChild) insertan relativo a un hijo existente, y RemoveChild(oldChild) o RemoveAllChildren() eliminan nodos. CompositeNode.IndexOf(child) devuelve la posición de un hijo. Sobre Node mismo, Clone(isCloneChildren) crea una copia — opcionalmente profunda — y Remove() separa el nodo de su padre. Node.NextPreOrder(rootNode) y PreviousPreOrder(rootNode) recorren el árbol en orden de documento, limitados por un nodo raíz dado, lo cual es una forma común de escanear todo un subárbol nodo por nodo sin código recursivo.

Colecciones de Nodos y Notificaciones de Cambios

NodeCollection es una colección concreta y mutable de nodos que admite Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node) y ToArray(), junto con una propiedad Count. NodeList y NodeEnumerator soportan iteración de estilo lectura sobre los resultados de consultas, como los nodos devueltos por SelectNodes. Para observar los cambios estructurales a medida que ocurren, implemente INodeChangingCallback — sus métodos NodeInserting(args), NodeInserted(args), NodeRemoving(args) y NodeRemoved(args) reciben cada uno un NodeChangingArgs que describe el Node afectado, su OldParent/NewParent, y el NodeChangingAction que ocurrió.

Visitando Nodos con DocumentVisitor

Para procesar cada tipo de nodo de forma estructurada sin recorrer manualmente el árbol, subclase DocumentVisitor y sobrescriba los métodos Visit*Start/Visit*End para los tipos de nodo que le interesen — VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun, etc. — luego invoque Node.Accept(visitor) en la raíz del subárbol para ejecutarlo. Este es el patrón estándar para exportar, transformar o inspeccionar un documento sin escribir un recorrido recursivo personalizado para cada caso de uso.


Consejos y Mejores Prácticas

  • Utilice NodeType.Any con GetChildNodes(NodeType.Any, true) cuando necesite cada descendiente sin importar el tipo, en lugar de llamar a GetChildNodes una vez por tipo.
  • Prefiera Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) para un escaneo plano y no recursivo de un subárbol en lugar de escribir una bajada recursiva manual.
  • Implemente DocumentVisitor en lugar de código ad hoc de recorrido de árbol cuando el mismo procesamiento debe ejecutarse contra muchos tipos de nodos.
  • Verifique Node.IsComposite antes de convertir a CompositeNode al trabajar con un nodo de tipo desconocido.
  • Registre un INodeChangingCallback cuando necesite reaccionar a inserciones o eliminaciones realizadas por otro código (incluidas operaciones integradas), no solo a los cambios que su propio código haga directamente.

Problemas comunes

ProblemaCausaSolución
Convertir un Node a CompositeNode lanza un error de conversión no válidaEl nodo es de tipo hoja (por ejemplo Run o FieldStart) que no puede contener hijosVerifique Node.IsComposite primero, o use NodeType para confirmar que el nodo es de tipo contenedor antes de castearlo
GetChildNodes solo devuelve hijos directosisDeep se dejó false (o se omitió donde la sobrecarga por defecto es superficial)Pase isDeep: true para buscar en todo el subárbol
Modificar una colección mientras se itera sobre ella genera un errorSe agregaron o eliminaron nodos dentro de un foreach sobre NodeCollection o el resultado de GetChildNodesMaterialice los nodos a modificar con ToArray() primero, luego itere sobre el arreglo mientras muta el árbol
Una subclase de DocumentVisitor no se llama para cierto contenidoEl visitante se invocó mediante Accept() en un nodo que no contiene el contenido objetivo, o el método Visit* correspondiente no fue sobrescritoLlame a Accept() en un ancestro que contiene el contenido, y sobrescriba cada par Visit*Start/Visit*End relevante para los tipos de nodo que se están procesando

FAQ

¿Cuál es la diferencia entre Node y CompositeNode?

Node es la clase base de la que derivan todos los elementos del documento. CompositeNode extiende Node para los elementos que pueden contener hijos (como Body, Paragraph y Table); los elementos hoja (como Run) derivan directamente de Node y no pueden contener hijos.

¿Cómo puedo encontrar todos los nodos de un tipo específico en un documento?

Llame a CompositeNode.GetChildNodes(nodeType, isDeep) en el nodo que inicia su búsqueda (a menudo el propio Document), pasando el NodeType que desea y isDeep: true para buscar en todo el subárbol, no solo en los hijos directos.

¿Cómo elimino nodos de forma segura mientras itero?

Recoja los nodos a eliminar en una matriz primero — por ejemplo con NodeCollection.ToArray() — luego itere sobre esa matriz y llame a Remove() en cada nodo, en lugar de modificar la colección en vivo mientras un foreach la está iterando.

¿Qué son los nodos FieldStart, FieldSeparator y FieldEnd?

Son valores NodeType que marcan la maquinaria de un campo de Word incrustado en el flujo de ejecución: FieldStart y FieldEnd delimitan el campo, y FieldSeparator separa el código del campo de su resultado mostrado actualmente.

¿Cómo proceso cada párrafo, tabla y run en un documento?

Subclase DocumentVisitor, sobrescriba VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd y VisitRun para los tipos de nodo que necesite, luego llame a Accept(visitor) en el Document (o cualquier raíz de subárbol) para ejecutarlo.


API Reference Resumen

Clase/MétodoDescripción
NodeClase base abstracta para cada nodo en un árbol de documentos
CompositeNodeClase base para nodos que pueden contener hijos
CompositeNode.GetChildNodes(nodeType, isDeep)Recuperar todos los hijos de un tipo dado, opcionalmente en todo el subárbol
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild)Agregar un nodo a un contenedor al final, o antes/después de un hijo existente
NodeTypeEnumeración que identifica el tipo de un nodo (Paragraph, Table, Run, FieldStart, etc.)
NodeCollectionColección concreta y mutable de objetos Node
Node.Clone(isCloneChildren)Copiar un nodo, opcionalmente incluyendo sus hijos
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode)Recorrer un subárbol en orden de documento
INodeChangingCallbackInterfaz de devolución de llamada notificada cuando se insertan o eliminan nodos
DocumentVisitorClase base para recorrido estructurado y despachado por tipo a través de Node.Accept(visitor)

Ver también

 Español