Integración de fragmentos XML y evaluación de rutas en contextos locales
En entornos de desarrollo que requieren manipulación avanzada de documentos estructurados, las operaciones básicas de recorrido suelen resultar insuficientes. Dos escenarios técnicos recurrentes son la inyección de nodos procedentes de archivos externos y la ejecución de consultas XPath acotadas a una rama específica. La biblioteca libxml2 ofrece las primitivas necesarias para resolver ambos casos, aunque el manejo preciso de los espacios de nombres (namespaces) y los contextos de evaluación exige una implementación cuidadosa para preservar la integridad del árbol.
Inserción de nodos desde una plantilla externa
Para incorporar un fragmento XML dentro de un documento existente, es necesario duplicar la estructura del nodo raíz de la plantilla, adaptar sus declaraciones de espacio de nombres al documento destino y vincularlo como hermano o hijo. El flujo de trabajo recomendado es:
- Cargar el documento plantilla desde disco.
- Localizar y clonar su nodo raíz mediante
xmlCopyNodeList(). - Recuperar el espacio de nombres del documento objetivo en la posición de inserción con
xmlSearchNs(). - Aplicar recursivamente dicho espacio de nombres a toda la subestructura clonada.
- Anexar la copia al árbol principal utilizando funciones de vinculación de nodos hermanos.
A continuación, se presenta una implementación modular en C que orquesta este proceso. El código localiza un punto de inserción, carga un archivo externo, normaliza los espacios de nombres y guarda el resultado.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
static void propagar_ns(xmlNodePtr subarbol, xmlNsPtr espacio_destino);
static int insertar_plantilla_en_nodo(xmlDocPtr doc_padre, xmlNodePtr punto_insercion, const char *ruta_plantilla);
static int registrar_espacios(xmlXPathContextPtr contexto, const char *lista_ns);
static void ejecutar_consulta_y_fusion(const char *doc_ruta, const char *consulta_xpath, const char *ns_prefijos);
/* Recorre iterativamente todos los nodos y asigna el espacio de nombres del destino */
static void propagar_ns(xmlNodePtr subarbol, xmlNsPtr espacio_destino) {
if (!subarbol) return;
xmlSetNs(subarbol, espacio_destino);
for (xmlNodePtr iter = subarbol->children; iter != NULL; iter = iter->next) {
propagar_ns(iter, espacio_destino);
}
}
/* Carga un archivo externo, copia su raíz y lo anexa como hermano siguiente */
static int insertar_plantilla_en_nodo(xmlDocPtr doc_padre, xmlNodePtr punto_insercion, const char *ruta_plantilla) {
xmlDocPtr doc_temp = xmlReadFile(ruta_plantilla, NULL, XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
if (!doc_temp) {
fprintf(stderr, "Fallo al leer plantilla: %s\n", ruta_plantilla);
return -1;
}
xmlNodePtr raiz_origen = xmlDocGetRootElement(doc_temp);
if (!raiz_origen) {
fprintf(stderr, "La plantilla carece de nodo raíz válido.\n");
xmlFreeDoc(doc_temp);
return -1;
}
xmlNodePtr copia = xmlCopyNodeList(raiz_origen);
xmlNsPtr ns_objetivo = xmlSearchNs(doc_padre, punto_insercion, NULL);
if (ns_objetivo) {
propagar_ns(copia, ns_objetivo);
}
xmlAddNextSibling(punto_insercion, copia);
xmlFreeDoc(doc_temp);
return 0;
}
static int registrar_espacios(xmlXPathContextPtr ctx, const char *lista) {
if (!ctx || !lista) return -1;
char *dup = strdup(lista);
char *ptr = dup;
while (*ptr) {
while (*ptr == ' ') ptr++;
if (*ptr == '\0') break;
char *prefijo = ptr;
char *sep = strchr(ptr, '=');
if (!sep) { free(dup); return -1; }
*sep = '\0';
char *uri = sep + 1;
char *espacio = strchr(uri, ' ');
if (espacio) *espacio = '\0';
if (xmlXPathRegisterNs(ctx, BAD_CAST prefijo, BAD_CAST uri) != 0) {
fprintf(stderr, "Error registrando NS: %s=%s\n", prefijo, uri);
free(dup);
return -1;
}
ptr = espacio ? espacio + 1 : "";
}
free(dup);
return 0;
}
static void ejecutar_consulta_y_fusion(const char *archivo, const char *xpath, const char *ns_lista) {
xmlDocPtr doc = xmlReadFile(archivo, NULL, XML_PARSE_NOBLANKS);
if (!doc) {
fprintf(stderr, "No se pudo abrir: %s\n", archivo);
return;
}
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
if (!ctx) {
xmlFreeDoc(doc);
return;
}
if (ns_lista && registrar_espacios(ctx, ns_lista) < 0) {
xmlXPathFreeContext(ctx);
xmlFreeDoc(doc);
return;
}
xmlXPathObjectPtr res = xmlXPathEvalExpression(BAD_CAST xpath, ctx);
if (res && res->nodesetval) {
int total = res->nodesetval->nodeNr;
int fusionado = 0;
for (int i = 0; i < total; ++i) {
xmlNodePtr nodo = res->nodesetval->nodeTab[i];
if (nodo->type == XML_ELEMENT_NODE && !fusionado) {
insertar_plantilla_en_nodo(doc, nodo, "plantilla.xml");
fusionado = 1;
}
}
xmlXPathFreeObject(res);
res = xmlXPathEvalExpression(BAD_CAST xpath, ctx);
if (res && res->nodesetval) {
printf("Nodos validados tras inserción: %d\n", res->nodesetval->nodeNr);
xmlXPathFreeObject(res);
}
}
xmlTreeIndentString = " ";
xmlSaveFormatFile(archivo, doc, 1);
xmlXPathFreeContext(ctx);
xmlFreeDoc(doc);
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Uso: %s <archivo.xml> <consulta_xpath> [lista_ns]\n", argv[0]);
return 1;
}
xmlInitParser();
ejecutar_consulta_y_fusion(argv[1], argv[2], argc > 3 ? argv[3] : NULL);
xmlCleanupParser();
return 0;
}
Evaluación de XPath en contextos locales
Cuando se requiere extraer información únicamente de una rama específica del árbol, evaluar la expresión desde la raíz puede generar resultados no deseados o ineficiencias. La función xmlXPathNodeEval() permite definir un nodo arbitrario como el contexto de referencia, restringiendo la búsqueda exclusivamente a su subárbol descendiente.
El siguiente fragmento demuestra cómo localizar un nodo objetivo y ejecutar una consulta XPath relativa a su estructura, manteniendo la compatibilidad con las declraaciones de espacio de nombres registradas previamente.
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
static void procesar_nodo_raiz(xmlDocPtr documento, const char *expresion, const char *prefijo, const char *uri) {
xmlNodePtr raiz = xmlDocGetRootElement(documento);
if (!raiz) return;
xmlNodePtr objetivo = raiz->children;
while (objetivo && xmlStrcmp(objetivo->name, BAD_CAST "libro") != 0) {
objetivo = objetivo->next;
}
if (!objetivo) {
fprintf(stderr, "No se encontró el nodo objetivo.\n");
return;
}
xmlXPathContextPtr ctx = xmlXPathNewContext(documento);
if (!ctx) return;
if (xmlXPathRegisterNs(ctx, BAD_CAST prefijo, BAD_CAST uri) != 0) {
fprintf(stderr, "Fallo al registrar el espacio de nombres.\n");
xmlXPathFreeContext(ctx);
return;
}
/* Evaluación restringida al contexto del nodo encontrado */
xmlXPathObjectPtr resultado = xmlXPathNodeEval(objetivo, BAD_CAST expresion, ctx);
if (!resultado || !resultado->nodesetval) {
printf("Sin coincidencias para la consulta local.\n");
} else {
int cantidad = resultado->nodesetval->nodeNr;
printf("Coincidencias en subárbol: %d\n", cantidad);
for (int k = 0; k < cantidad; ++k) {
xmlNodePtr elem = resultado->nodesetval->nodeTab[k];
xmlChar *texto = xmlNodeGetContent(elem);
printf(" -> %s\n", texto ? (char*)texto : "(vacío)");
xmlFree(texto);
}
}
xmlXPathFreeObject(resultado);
xmlXPathFreeContext(ctx);
}
int main(void) {
xmlDocPtr doc = xmlReadFile("catalogo.xml", NULL, XML_PARSE_NOBLANKS);
if (!doc) {
fprintf(stderr, "Error al cargar el documento XML.\n");
return 1;
}
const char *ruta = "ns:autor/ns:nombre";
const char *pref = "ns";
const char *espacio = "urn:catalogo-schema";
procesar_nodo_raiz(doc, ruta, pref, espacio);
xmlFreeDoc(doc);
xmlCleanupParser();
return 0;
}
Formato y validación con xmllint
Los documentos modificados programáticamente suelen perder la sangría original, dificlutando la lectura manual. La utilidad de línea de comandos xmllint, distribuida junto con libxml2, permite reindentar archivos automáticamente. La combinación de parámetros --format y --output reescribe el contenido con una estructura jerárquica limpia. Para proyectos que manejan espacios de nombres complejos, se recomienda validar las rutas mediante código nativo, ya que las herramientas de terminal pueden presentar limitaciones en la resolución de prefijos durante consultas interactivas.