XmlTextReader (o cómo leer ficheros XML lo más rápidamente posible)

Desde el principio, uno de los principales puntos a tratar del equipo de ASP.NET ha sido todo lo concerniente a XML. Ahora, con la versión 2.0 la eficiencia a la hora de tratar archivos XML es mucho mayor de la ya de por sí buena en la versiones 1.x.

En este artículo vamos a tratar el XmlTextReader, y para empezar, qué mejorar que un listado de ventajas y desventajas de su uso:

Ventajas:
1.- Es un puntero "forward-only", que simplemente va leyendo hacia adelante y que no carga el fichero entero para ello. Por tanto, es muy eficiente en cuanto a uso de memoria.
2.- En un momento dado, en cualquier parte de la lectura del fichero, el XmlTextReader puede extraer alguno de los elementos del XML de forma independiente.
3.- Es el lector de XML más rápido que podemos encontrar.

Desventajas:
1.- Al ser "forward-only", no podemos ir hacia atrás nunca :)
2.- No puede editar directamente el fichero XML que está leyendo.
3.- Su uso es menos intuitivo que otros elementos que trabajan con XML.

Por tanto, el XmlTextReader es aconsejable usarlo cuando lo único que queremos es simplemente leer un fichero XML, y cuanto más largo sea éste, más se notará la diferencia con respecto a otros objetos de lectura de XML.

Por ejemplo, imaginemos que tenemos el siguiente fichero XML

fichero.xml
<raiz>
    <persona>
       <nombre>
          Juan
       </nombre>
       <apellidos>
          Martínez Martínez
       </apellido>
    </persona>
    ...
</raiz>


Si nosotros queremos averiguar los apellidos de la primera persona que se llame Vicente, procederemos del siguiente modo:

ejemploXML.cs
string apellidosdeVicente = string.Empty;
using (XmlTextReader reader = new XmlTextReader(pathFichero))
{
    reader.MoveToContent();
    reader.ReadStartElement();
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "nombre")
        {
             if (reader.ReadString() == "Vicente")
                {
                    reader.ReadToNextSibling("apellidos");
                   
apellidosdeVicente = reader.readString();
                    break;
                }
        }
    reader.Close();
}


Lo primero que cabe destacar es que usamos la sentencia using, por lo que se cerrará todo lo necesario y se liberará toda la memoria pertinente en el caso de que haya un error en la lectura. También observamos que cuando terminamos de leer el fichero, cerramos el XmlTextReader.

Con las dos primeras sentencias le decimos a reader que se mueva al nodo raíz de nuestro fichero, y a partir de ese momento, entramos en un bucle while del que, en principio, sólo saldríamos cuando se haya alcanzado el final del documento.

Una vez dentro del bucle entramos en una primera "criba" en forma de sentencia if. Lo que estamos comprobando es que el nodo en que estamos sea del tipo elemento (podría ser un atributo, podría ser el final de un elemento), y si es así comprobar que el nombre de ese elemento sea "nombre" (ojo porque XML es case sensitive).

Si se cumplen ambas condiciones, leemos cuál es el nombre en cuestión, comprobación que hemos puesto en otro if para dejar un código más claro (lo podríamos haber incluído en el if previo). Si realmente se trata de nuestro Vicente, viajamos al elemento "apellidos", leemos estos y salimos del while con una sentencia break.

Cabe hacer hincapié en que:
- reader.ReadString(): lee el valor del elemento en forma de string.
- reader.ReadToNextSibling("apellidos"): lee el siguiente elemento "hermano" con nombre "apellidos". En este caso, podríamos haber puesto ReadToFollowing en lugar de ReadToNextSibling. La diferencia entre ambos es que ReadToFollowing("apellidos") viaja hacia el siguiente elemento de nombre "apellidos", y ReadToNextSibling("apellidos") lo hace sólo cuando ese elemento es su hermano.

¿Qué significa que lea el siguiente elemento hermano?
Dada la estructura de un fichero XML, por nodos hermanos se conoce a aquellos que tienen el mismo padre, jeje .
Es decir, el nodo padre de nombre y de apellidos es persona, por lo que nombre y apellidos son hermanos, pero en la siguiente variación del ejemplo anterior...

<raiz>
    <persona>
       <nombre>
          Juan
       </nombre>
       <maridoDe>
          <nombre>
             María
          </nombre>
          <apellidos>
             García
          </apellidos>
       </padre>
       <apellidos>
          Martínez Martínez
       </apellido>
    </persona>
    ...
</raiz>

... vemos que hay otro elemento apellidos en el nombre y los apellidos de Juan, por lo que
1.- Si hubiéramos puesto ReadToFollowing, hubiéramos viajado a los apellidos de la mujer de Juan, NO a los apellidos de Juan.
2.- Poniendo ReadToNextSibling, no sólo viajamos a los apellidos de Juan, sino que nos ahorramos que el XmlTextReader se piense nada que no sea descendiente directo de "persona". En este ejemplo no importa demasiado, pero imaginad que en lugar de "MaridoDe", tengamos que poner "vecinoDe" con todos los nombres y apellidos de sus vecinos .

Bueno, pues lo dicho, esto es un ejemplillo simplón, pero con XmlTextReader podréis hacer muchas cosas interesantes. Entre ellas, destaco dos cosas:
1.- Se puede recoger el dato en el el tipo de datos que queramos. Es decir, además de ReadString() (el típico), se puede poner reader.ReadElementContentAsInt() o reader.ReadElementContentAsBoolean(), y así con los tipos de datos más típicos.
2.- Con ReadSubtree(), recogemos un XmlReader con el nodo en el que estamos y todos sus descendientes. Por ejemplo, es muy útil para encontrar un nodo sobre el que queremos trabajar y mandarlo a una función auxiliar que lo trate, de modo totalmente independiente al XmlTextReader inicial.