CodeGym /Cursos /C# SELF /Trabajo avanzado con XML: ...

Trabajo avanzado con XML: XmlDocument y XPath

C# SELF
Nivel 48 , Lección 2
Disponible

1. Introducción

Imagínate un XML gigantesco que te envía algún servicio SOAP o un API bancario. Tiene cientos de elementos anidados, estructuras heterogéneas, y necesitas encontrar solo una única suma de pago del mes pasado. La técnica de serialización no sirve aquí — es demasiado pesada y no sabes de antemano dónde mirar. Hace falta una "lupa" para el árbol XML y la habilidad de moverse rápido por sus ramas.

Justo para estos casos en .NET existe desde hace años una herramienta potente — la clase XmlDocument, complementada por el lenguaje de consultas XPath. Hoy aprenderás a:

  • Cargar XML en un árbol DOM;
  • Moverte y encontrar elementos a mano y usando XPath;
  • Modificar el contenido de un documento XML;
  • Añadir, eliminar y cambiar nodos;
  • Usar XPath para selecciones complejas.

2. ¿Qué es XmlDocument?

XmlDocument es una clase del namespace System.Xml. Es una implementación del modelo DOM (Document Object Model) — la representación de todo el archivo XML como un árbol de objetos en memoria.

Un poco de teoría y analogías

Si XmlSerializer se parece a un constructor LEGO, cuando conviertes un objeto en un conjunto de piezas y viceversa, entonces XmlDocument es como trabajar con un árbol real: tiene una raíz, ramas (elementos), hojas (nodos de texto), y puedes recorrer ese árbol, añadir ramas, cortar hojas y trasplantarlas donde quieras.

Carga simple de un documento XML

Supongamos que tenemos este XML:

<users>
  <user id="1">
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>Igor</name>
    <age>35</age>
  </user>
</users>

Cargémoslo en memoria:

using System.Xml;

string xml = @"
<users>
  <user id='1'>
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id='2'>
    <name>Igor</name>
    <age>35</age>
  </user>
</users>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // o doc.Load("ruta_al_archivo.xml");

Después de llamar a LoadXml (o Load, si el XML viene de un archivo) tienes acceso completo al contenido del documento.

3. Navegación por el árbol DOM

Estructura del árbol DOM

Cada documento XML tras cargarse se convierte en un árbol compuesto por nodos de distintos tipos:

Tipo de nodo Clase en .NET Ejemplo
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Olga, 28
Comment XmlComment
<!-- comentario -->

Para acceder a los elementos necesarios tendrás que "caminar por el árbol" usando propiedades como ChildNodes, Attributes, ParentNode, etc.

Obtenemos el elemento raíz

XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users

Iteramos elementos hijos

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — es <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"Usuario {id}: {name}, edad {age}");
    }
}

IMPORTANTE: El acceso user["name"] es posible solo si entre los hijos directos existe el elemento <name>.

Acceso a atributos y texto

var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Olga"

4. Modificación del documento XML

Cambiar un valor

Supongamos que Olga de repente decidió "rejuvenecer":

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // ahora <age>22</age>

Añadir un nuevo usuario

XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");

XmlElement name = doc.CreateElement("name");
name.InnerText = "Vasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);

Eliminar un elemento

Eliminemos al segundo usuario ("Igor"):

XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
    root.RemoveChild(userToDelete);

Guardar cambios

doc.Save("users_updated.xml");
// O doc.OuterXml — para obtener la cadena con el XML

5. XPath — lenguaje de búsqueda y selección en XML

Trabajar con el árbol DOM se vuelve pesado si necesitas buscar elementos "por condición" — por ejemplo, todos los usuarios mayores de 25 años. Para esto existe XPath — un lenguaje para navegar por el árbol XML.

Uso básico de XPath

Las consultas XPath se ejecutan con los métodos SelectSingleNode (devuelve el primer nodo encontrado) y SelectNodes (devuelve una colección de nodos).

Ejemplo: encontrar el usuario con id = 1

XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Olga

Ejemplo: encontrar todos los usuarios mayores de 25 años

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // Mostrará Olga e Igor (si Igor aún no fue eliminado)
}

XPath — sintaxis breve

Expresión XPath Qué hace
/users/user
Todos los elementos <user> en la raíz <users>
user[@id='3']
Usuario con id=3
user[age>25]
Usuarios mayores de 25 años
user/name
Todos los elementos <name> de todos los usuarios
user[last()]
El último <user>
user[position()<3]
Los dos primeros <user>

Más ejemplos y sintaxis: Documentación de XPath.

Otro ejemplo: selección por elementos anidados

Supongamos que el XML es más complejo:

<library>
  <book>
    <title>El señor de las moscas</title>
    <author>
      <firstname>William</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>A la sombra de las muchachas en flor</title>
    <author>
      <firstname>Marcel</firstname>
      <lastname>Proust</lastname>
    </author>
  </book>
</library>
XmlDocument doc = new XmlDocument();
doc.LoadXml(libraryXml);
XmlNodeList authors = doc.SelectNodes("/library/book/author/lastname");
foreach (XmlNode lastName in authors)
    Console.WriteLine(lastName.InnerText);

Qué se imprimirá:

Golding
Proust

6. XPath: filtrado, lógica, cálculos

Filtros lógicos

Imprimir los nombres de todos los usuarios cuya edad está entre 18 y 30:

// [age>=18 and age<=30]
XmlNodeList youngUsers = root.SelectNodes("user[age>=18 and age<=30]");
foreach (XmlNode u in youngUsers)
    Console.WriteLine(u["name"].InnerText);

Trabajo con atributos

Los atributos se seleccionan con @. Encontraremos usuarios cuyo id empieza por "1":

XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");

Contar nodos

Directamente en el método C# SelectNodes no puedes usar la función count() para obtener un número — devuelve una colección, no un número. Pero puedes hacer:

int count = root.SelectNodes("user").Count;

Filtros anidados

XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Golding']");

7. XPath y espacios de nombres

Si tu XML usa espacios de nombres (xmlns), todo se complica un poco. Para esos casos usa XmlNamespaceManager:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Algoritmos</title>
  </book>
</catalog>
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("b", "http://books.example.com");

XmlNodeList books = doc.SelectNodes("/b:catalog/b:book", nsmgr);
foreach (XmlNode book in books)
    Console.WriteLine(book.SelectSingleNode("b:title", nsmgr)?.InnerText);

8. Modificar XML con DOM

Añadir nodos "sobre la marcha"

Añadiremos un nuevo elemento <email> al primer usuario:

XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);

Cambiar atributos

olga.SetAttribute("id", "99"); // Ahora id="99"

Eliminar nodos usando XPath

XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
    root.RemoveChild(node);

9. Novedades útiles

Búsqueda y modificación masiva de estructura

Supongamos que recibes un XML grande con distintos elementos <record type="...">. Necesitas dejar solo aquellos cuyo atributo type sea "customer", y añadir a cada uno un hijo <status>active</status>.

XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
    XmlElement status = doc.CreateElement("status");
    status.InnerText = "active";
    record.AppendChild(status);
}

Árbol de nodos XML (DOM)


users
├─ user (id="1")
│   ├─ name ("Olga")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("Igor")
│   └─ age ("35")

Ejemplo de selección con XPath

Consulta XPath Qué devolverá
/users/user[1]
El primer usuario (id="1")
/users/user[last()]
El último usuario (id="2")
/users/user[age>30]
Todos los mayores de 30 años (Igor)
/users/user[@id='2']
Usuario con id="2"

Aplicación práctica

  • Integraciones con APIs "antiguas". En muchas corporaciones y bancos los servicios SOAP/XML aún dominan. Poder encontrar y cambiar rápido algo en una gran respuesta XML puede ahorrar decenas de horas.
  • Migración de datos. Al pasar de un sistema a otro a menudo hay que parsear XML y hacer selecciones complejas, transformaciones y cambios masivos.
  • Importación-exportación a Excel. En muchos productos B2B la entrada sigue siendo XML. Ahí XPath es una forma rápida de sacar lo necesario sin crear un modelo de datos grande.

10. Errores, matices y trampas típicas

NullReferenceException: Si intentas acceder a un elemento inexistente, por ejemplo el["something"].InnerText, y ese hijo no existe en absoluto. Revisa siempre por null.

XPath y anidamiento: Recuerda que una expresión sin la barra inicial / busca entre los descendientes del nodo actual, mientras que con la / — desde la raíz del documento. Diferentes puntos de referencia pueden producir ausencia de resultados aunque los datos existan.

Trabajo con espacios de nombres: Si no usas XmlNamespaceManager, las consultas XPath contra XML con xmlns devolverán vacío.

Modificar durante la iteración: Si quieres eliminar nodos resultantes de SelectNodes, primero guárdalos en un array y luego itera — si no, la colección cambia durante la iteración y pueden surgir errores.

Diferencia entre nodos de texto y nodos de elemento: Entre elementos puede haber nodos de texto — espacios y saltos de línea que XML considera contenido. Para selecciones estrictas usa solo XmlElement o filtra por NodeType.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION