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.AnyconGetChildNodes(NodeType.Any, true)cuando necesite cada descendiente sin importar el tipo, en lugar de llamar aGetChildNodesuna 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
DocumentVisitoren lugar de código ad hoc de recorrido de árbol cuando el mismo procesamiento debe ejecutarse contra muchos tipos de nodos. - Verifique
Node.IsCompositeantes de convertir aCompositeNodeal trabajar con un nodo de tipo desconocido. - Registre un
INodeChangingCallbackcuando 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
| Problema | Causa | Solución |
|---|---|---|
Convertir un Node a CompositeNode lanza un error de conversión no válida | El nodo es de tipo hoja (por ejemplo Run o FieldStart) que no puede contener hijos | Verifique Node.IsComposite primero, o use NodeType para confirmar que el nodo es de tipo contenedor antes de castearlo |
GetChildNodes solo devuelve hijos directos | isDeep 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 error | Se agregaron o eliminaron nodos dentro de un foreach sobre NodeCollection o el resultado de GetChildNodes | Materialice 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 contenido | El visitante se invocó mediante Accept() en un nodo que no contiene el contenido objetivo, o el método Visit* correspondiente no fue sobrescrito | Llame 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étodo | Descripción |
|---|---|
Node | Clase base abstracta para cada nodo en un árbol de documentos |
CompositeNode | Clase 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 |
NodeType | Enumeración que identifica el tipo de un nodo (Paragraph, Table, Run, FieldStart, etc.) |
NodeCollection | Colecció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 |
INodeChangingCallback | Interfaz de devolución de llamada notificada cuando se insertan o eliminan nodos |
DocumentVisitor | Clase base para recorrido estructurado y despachado por tipo a través de Node.Accept(visitor) |