Tutorial de XML
Página principal del curso de C#
Tutorial de C#, de Pedro Castillo
Otros cursos y tutoriales: comercio electrónico, WAP, Webmaster
Página principal del grupo GeNeura

Tutorial entrada/salida y formatos XML en C#

J. J. Merelo

Tarde o temprano, un programa que se precie de serlo tiene que acabar interaccionando con el sistema de ficheros, escribiendo o leyendo algo. Las bases de datos están bien, pero para hacer cosas simples, nada mejor que un simple fichero.

Todos los ejemplos de esta página han sido comprobados en una RedHat 8.0 con el compilador y CLI Mono C# compiler version 0.23.0.0. Debieran funcionar con otras implementaciones con C#; si encontráis algún problema, por favor avisadme.

La entrada/salida (E/S) en C# está organizada alrededor del concepto de stream, o canal, igual que sucede en otros lenguajes como el Java. Un canal puede corresponder a un fichero en disco, pero también a una cadena o a otras cosas más esotéricas, como canales de comunicaciones entre procesos; en este tutorial explicaremos el concepto de canal y lo aplicaremos principalmente a escritura y lectura de ficheros en disco

Contenido de esta sección
  • Apertura de ficheros: FileStream
  • Lectura de ficheros byte a byte: ReadByte
  • Lectura con buffer.
  • Ficheros de texto

El protagonista de este tutorial tuvo la suerte de ser contratado en una gran empresa, con un contrato fijo blindado, opciones de compra de acciones, y regalo de una PlayStation, en la época de la burbuja puntocom. Cuando se pinchó la burbuja, entre las opciones de compra que tenía (que ya no valían un duro), y que le habían tomado cariño de la dirección, a pesar de que no había trabajo para él, lo mantuvieron en nómina porque se hizo imprescindible: era el que llevaba la quiniela de la empresa. Pero, claro, le aplicaba siempre las últimas tecnologías en software, así que decidió usar en C# para escribir la aplicación definitiva de quiniela oficinista. Pero la fue haciendo poquito a poquito, que eso del C# era nuevo para él. Como ya tenía muchos ficheros de quiniela almacenados, empezó por ver la posibilidad de leer esos ficheros, lo cual hizo con este programa (file1.cs; conversión a HTML cortesía de SSW)

using System;
  using
System.IO;
  public class file1 {
    public static void
Main(){
      String name = "file1.cs" ;
      FileStream s2 = new FileStream (name, FileMode.Open , FileAccess.Read , FileShare.Read );
      while
(s2.Length > s2.Position ){
        Console.Write (( char )s2.ReadByte () ) ;
      }
    }
  }

Este primer programa es una especie de "hola mundo" de la entrada/salida en C#. Lo único que hace es abrir como fichero de entrada su propio código fuente, e imprimirlo en la salida. Pero vayamos por partes.

Para empezar, el espacio de nombres donde se encuentran la mayoría de las clases de entrada salida se llama, sin demasiada imaginación, System.IO; es necesario incluirlo.

De ahí se sacan clases como FileStream, un stream para escribir en fichero. Este es un fichero sin estructura, que se puede leer, en principio, de forma secuencial. Para abrirlo, hay que pasarle al constructor de la clase, además del nombre de fichero:

Es una forma un tanto prolija de abrir un fichero, por eso hay formas más simples: la clase tiene constructures que permiten especificar sólo uno de los tres parámetros, y usar los valores por defecto de los otros dos, por ejemplo:

      FileStream = newFileStream (name, FileMode.OpenOrCreate );

En este caso se abriría un fichero (o se crearía si no existiera) con acceso de lectura y escritura, y con modo Read de compartición de recursos.

Una vez abierto el fichero, es cuestión de leerlo; como el fichero no tiene estructura, lo más fácil es irlo leyendo byte a byte, y presentándolo en pantalla (o cualquier otra cosa un poco más seria que queramos hacer). La lectura se realiza en un bucle while, que lee mientras que la longitud Length del canal es mayor que la posición Position del cursor de lectura. Cada byte se lee usando ReadByte(), una función sin parámetros que devuelve el byte situado en el cursor de lectura en el canal. Como alternativa, se puede usar la función Read(), que lee un bloque de bytes de tamaño especificado de antemano, y devuelve el número de bytes leidos efectivamente. Puede ser un poco más rápida, pero para el caso que nos ocupa, da igual.

Se puede cambiar este fichero a una lectura con buffer, que teóricamente es más eficiente, pero no porque sea más rápida, sino porque es más buena persona con el resto de los recursos del sistema: hace menos llamadas al sistema operativo, porque la lectura la hace a través de lecturas de bloques de bytes. (file1a.cs):

      BufferedStream = new BufferedStream (s2);
      while
(bs.Length > bs.Position ){
        Console.Write (( char )bs.ReadByte () ) ;

En este ejemplo, en el que sólo se muestran las líneas relevantes, al fichero se le arropa alrededor un canal con buffer, y se lee de la misma forma, con la única diferencia que se lee del canal enbuferado, y no del fichero original. Las pruebas que yo he hecho me demuestran que esta forma de leer tarda casi el doble de lo que lo hacía la original, pero doctores tiene la Iglesia, y si se dice que es más eficiente, pues lo será, ¿no?

Sin embargo, hay una forma más eficiente todavía de leer ficheros de texto, y es suponer que efectivamente se trata de ficheros de texto. Lo hacemos en el siguiente ejemplo file2.cs:

  using System;
  using
System.IO;
  public class file2 {
    public static void
Main( string []args ){
      StreamReader lector= File.OpenText (args[ 0 ] ) ;
      string
linea ;
      do

      {
        linea = lector.ReadLine () ;
        if
(linea != null ){
          Console.WriteLine (linea) ;
        }
      }
      while
(linea != null );
      lector.Close () ;
    }

  }

En este ejemplo, que casualmente lee ficheros mucho más rápidamente que cualquiera de los otros, se usan nuevas clases específicas para lectura de ficheros de texto. Para empezar, está StreamReader, una clase que hereda de TextReader, y que lee caracteres de un fichero usando una codificación determinada; en este caso, se usa iso-8859-1 (o latin1), la codificación habitual en nuestras latitudes. Mientras que estas clases están diseñadas para entrada de caracteres, los canales en general están diseñados para entrada/salida de bytes. Pero, ¿no son lo mismo? Pues no. Desde que existe Unicode, los caracteres pueden tener más de un byte, dependiendo del alfabeto que esté codificado. En cualquier caso, también se tiene en cuenta el concepto de línea, con lo cual empieza a ser posible leer líneas completas, no sólo caracteres que incluyen, posiblemente, el fin de línea.

Esto de las codificaciones es un follón, pero se trata de lo siguiente: una vez que los caracteres se pueden codificar en varios bytes, hay varios órdenes posibles: uno delante y otro detrás, o viceversa; y además, hay diferentes formas posibles de acomodar codificaciones antiguas, como la ASCII. Bueno, pues para eso se ha inventado el UTF8 la forma más popular de codificar Unicode. La mayor parte de los lenguajes hoy en día pueden manejarlo, y simplemente hay que tener en cuenta si un canal o no está codificado usando esa codificación en caso de que haya que convertirlo a otra, como por ejemplo iso-8859-1.

Se usa para inicializar esa clase un método estático de la clase File, File.OpenText, que abre un fichero existente que usa la codificación UTF 8 para lectura. Vamos, un fichero de texto de toda la vida, para entendernos.

Posteriormente, en el bucle se usa la esperable función ReadLine para leer el fichero línea a línea, y escribirlo en la salid estándar. Al final, como mandan los cánones, se cierra el canal con una función apropiadamente llamada Close.

Sorprendentemente, o quizás no, este programa de lectura de ficheros resulta unas diez veces más rápido que los dos anteriores.

Finalmente, no nos debemos olvidar de nuestro objetivo final: hacer esa quiniela de la oficina por la cual, después de todo, nos están pagando nuestro sueldo. Así que habrá que escribir también los ficheros, usando un programa como el siguiente:file3.cs

  using System;
  using
System.IO;

  public class
file2 {
    public static void
Main( string []args ){
      StreamWriter SW;
      SW = File.CreateText (args[ 0 ] ) ;
      Console.Write ( "?Quién juega en casa? " ) ;
      String buffer;
      buffer = Console.ReadLine () ;
      SW.Write (buffer, " " ) ;
      Console.Write ( "?Quién juega fuera? " ) ;
      buffer = Console.ReadLine () ;
      SW.Write (buffer, " " ) ;
      Console.Write ( "?Qué pronóstico? " ) ;
      buffer = Console.ReadLine () ;
      SW.WriteLine (buffer) ;
      SW.Close () ;
      Console.WriteLine ( "Fichero creado" ) ;
    }

  }

En este fichero, que usa en vez de un lector como en el ejemplo anterior, un StreamWriter. Esta vez, en vez de abrir un fichero como hemos hecho anteriormente con OpenText, lo creamos con File.CreateText; hay otros modos de abrir ficheros de texto, pero no se va a tratar ahora de ellos.

Finalmente, el programa hace una serie de preguntas, y escribe las respuestas en una sola línea en un fichero, escribiendo un mensaje cuando acaba de hacer preguntas.

Ejercicios
1. Cambiar los dos programas anteriores para que lean usando el comando Read, y hacer pruebas de velocidad, a ver si se observa alguna diferencia.
2. Hacer un programilla que lea de un fichero las preguntas de una encuesta, las haga por consola (o cualquier otro medio), y almacene las respuestas en un fichero cuyo nombre debería ser diferente en cada caso.

Contenido de esta sección
  • Información sobre ficheros
  • Trabajando con directorios

Ya que vamos por buen camino, vamos a sacarle un poco más de jugo a la cosa. Nuestro amigo se crea una clase Partido que contiene toda la lógica necesaria para almacenar los partidos; como no tiene ningún elemento nuevo (de E/S, vamos), pues simplemente ponemos un enlace. Esta clase permite dar valores al que juega en casa, al visitante, y poner y recuperar los pronósticos para el partido de varias formas. Usando esa clase, hace un programilla que lee un fichero tal como este, crea un objeto por partido, e imprime los partidos leidos en pantalla (Quiniela.cs):


  using
quiniela.Partido;
  using
System;
  using
System.IO;
  using
System.Collections;
  // para ArrayList

  public class
Quiniela {
    public static void
Main( string []args ){
      FileInfo fichero = new FileInfo (args[ 0 ] );
      StreamReader stream = fichero.OpenText () ;
      ArrayList estaQuiniela = new ArrayList ();
      String linea;
      do

      {
        linea = stream.ReadLine () ;
        if
(linea != null ){
          String [] = linea.Split ( ' ' ) ;
          Console.Write ( "Valores\n" ) ;
          for
( int i = 0 ;i < valores.Length ;i++ ) {
            Console.Write ( "{0} => {1}\n " , i, valores[i] ) ;
          }
          Partido estePartido = new Partido (valores[ 0 ] , valores[ 1 ] , valores[ 2 ] );
          estaQuiniela.Add (estePartido) ;
        }
      }
      while
(linea != null );
      Console.Write ( "Partidos leidos: \n" ) ;
      for
( int i = 0 ;i < estaQuiniela.Count ;i++ ) {
        Partido = (Partido )estaQuiniela[i] ;
        Console.Write ( "{0}\n" , unPartido.getAsString () ) ;
      }
    }

  }

Este partido hay que compilarlo de la forma siguiente:

bash% mcs Quiniela.cs Partido.cs

Una de las pocas clases nuevas que se introducen en este programa es la clase FileInfo, una clase con la cual se pueden crear objetos que contienen información sobre un fichero determinado. En cierto sentido, equivale a un filehandle, pero la equivalencia no es total, porque un filehandle es un fichero abierto, y un objeto FileInfo simplemente contiene información sobre un fichero que existe; es decir, mientras que los métodos de la clase File son estáticos, los de la clase FileInfo son métodos de objeto; los mismos métodos estáticos de aquella clase se usan sobre objetos de esta clase. Eso precisamente es lo que hacemos con el método OpenText, que habíamos usado anteriormente como File.OpenText; en este caso es un método del objeto fichero que hemos creado anteriormente.

El resto es más o menos normal: usamos un objeto de tipo ArrayList para almacenar los objetos de tipo Partido, y finalmente, recorremos ese objeto y vamos imprimiento los partidos uno por uno usando el método getAsString() de la clase Partido.

Pero claro, si todo el mundo se pone rellenar quinielas, al final se encuentra uno con un mogollón de ficheros, en un o varios directorios, que habrá que ir procesando para sacarles la chicha. Y para eso hay que trabajar con directorios, como se hace en el siguiente programa (file4.cs), que es una especie de dir pachanguerillo en C#:


  using
System;
  using
System.IO;

  public class
file4 {
    public static void
Main(){
      DirectoryInfo dir = new DirectoryInfo ( "." );
      FileInfo [] files = dir.GetFiles () ;
      Console.WriteLine ( "Nombre\t|Extension\t|Ult. Acceso\t|Tamano\t" ) ;
      for
( int = 0 ;i < files.Length ;i++ ) {
        Console.WriteLine ( "{0}\t|{1}\t|{2}\t|{3}" , files[i] .Name , files[i] .Extension , files[i] .LastAccessTime , files[i] .Length ) ;
      }
    }
  }

Este programa, que no intenta ser útil (y de hecho, no lo es), sino simplemente mostrar qué se puede hacer con directorios en C#, abre un objeto de tipo DirectoryInfo.equivalente al FileInfo visto anteriormente, es decir, una instancia en memoria de un directorio que existe (ambos, de hecho, descienden de la clase FileSystemInfo, que es de demasiado bajo nivel para que tenga algún interés aquí).

Una de las cosas que se puede hacer con un objeto de ese tipo es obtener los ficheros que se alojan en tal directorio, usando el método GetFiles, que devuelve un vector de objetos de tipo FileInfo (visto anteriormente).

Sobre ese vector, hacemos un bucle de los de toda la vida, imprimiendo información sobre cada uno de ellos: nombre (Name), extensión (Extension), momento en el que se accedió por última vez, (LastAccessTime), y tamaño (Length). Lo separamos por tabuladores para que quede un poco más chulo, pero, aún así, no lo conseguimos.

Aún así, con los conocimientos adquiridos, ya se puede hacer un programilla que escoja todos los ficheros quinielísticos de un directorio, y vaya contando los pronósticos, para hacer estadístocas. Uno tal como este (QuinielaStats.cs):

using System;
  using
System.IO;
  public class
QuinielaStats {
    const
String unoequisdos = "1X2" ;
    public static void
Main(){
      DirectoryInfo dir = new DirectoryInfo ( @"." );
      FileInfo [] files= dir.GetFiles () ;
      int
[, ] pronosticos = newint [ 3 , 3 ];
      for
( int = 0 ;i < 3 ;i++ ) {
        for
( int = 0 ;j < 3 ;j++ ) {
          pronosticos[i, j] = 0 ;
        }
      }
      for
( int = 0 ;i < files.Length ;i++ ) {
        if
(files[i] .Name .StartsWith ( "quiniela" ) && files[i] .Name .EndsWith ( ".dat" ) ){
          Console.Write ( "Procesando {0}\n" , files[i] .Name ) ;
          StreamReader = files[i] .OpenText () ;
          String ;
          int
= 0 ;
          do

          {
            linea = stream.ReadLine () ;
            if
(linea != null ){
              String [] = linea.Split ( ' ' ) ;
              for
( int = 0 ;j < 3 ;j++ ) {
                if
(valores[ 2 ] .IndexOf (unoequisdos[j] ) >= 0 ){
                  pronosticos[contador, j] ++ ;
                }
              }
            }
            contador++ ;
          }
          while
(linea != null );
        }
      }
      for
( int = 0 ;j < 3 ;j++ ) {
        Console.Write ( "Pronosticos partido {0} -> " , j) ;
        for
( int = 0 ;k < 3 ;k++ ) {
          Console.Write ( "{0}= {1} " , unoequisdos[k] , pronosticos[j, k] ) ;
        }
        Console.Write ( "\n" ) ;
      }
    }

  }

Este programa no tiene gran misterio, salvo que escoge el fichero basado en su nombre (tiene que comenzar con quiniela y terminar con .dat), divide cada una de las líneas del fichero usan Split, y, finalmente, imprime cuál es la frecuencia de cada uno de los prónosticos para cada partido.

Nuestro amigo está cada vez más cerca de su objetivo. Desgraciadamente, los programas son poco amistosos para el usuario, usan un formato un tanto peculiar, y a la gente no le gusta responder tantas cosas sin usar el ratón. Pero no hay problema. Tenemos el XML.

Ejercicios
1. Hacer un programa que recoja las respuestas a las preguntas a la encuesta mencionada en el bloque de ejercicios anterior, y calcule estadísticas sobre las mismas.
2. Hacer un programa que saque un directorio, y permita hacer operaciones sobre los ficheros tales como borrarlo y visualizarlo. Ya sé que va a quedar cutre, pero es para que se vean los diferentes métodos de la clase FileInfo.

Contenido de esta sección
  • Escribiendo XML
  • Leyendo XML

Tras echar un somero vistazo a este magnífico tutorial de XML, nuestro amiguete, currante donde los haya, se pone a pensar que lo mejor es usar un formato para almacenar la información de forma estructurada. ¿Y qué mejor que usar XML? Pues allá que vamos: un programa para reescribir los ficheros que ya tenía creados en XML (QuinielaXML.cs):

using quiniela.Partido;
using
System;
using
System.IO;
using
System.Xml;
using
System.Cagonto;
using
System.Collections;

public class
Quiniela {
  public static void
Main ( string []args ){
    FileInfo fichero =
new FileInfo (args[ 0 ] );
    StreamReader stream = fichero.OpenText () ;
    ArrayList estaQuiniela =
new ArrayList ();
    String linea;

    do

    {
      linea = stream.ReadLine () ;

      if
(linea != null ){
        String [] valores= linea.Split (
' ' ) ;
        Partido estePartido =
new Partido (valores[ 0 ] , valores[ 1 ] , valores[ 2 ] );
        estaQuiniela.Add (estePartido) ;
      }
    }

    while
(linea != null );
    XmlTextWriter escritor =
new XmlTextWriter ( "quiniela.xml" , new System.Text.ASCIIEncoding ());
    escritor.Formatting = Formatting.Indented ;
    escritor.WriteStartDocument () ;
    escritor.WriteStartElement (
"quiniela" ) ;
    for
( int = 0 ;i < estaQuiniela.Count ;i++ ) {
      Partido unPartido = (Partido )estaQuiniela[i] ;
      escritor.WriteStartElement (
"partido" ) ;
      WriteEquipo(escritor, unPartido.getJuegaEnCasa () ,
"casa" ) ;
      WriteEquipo(escritor, unPartido.getJuegaFuera () ,
"fuera" ) ;
      escritor.WriteElementString (
"resultado" , unPartido.getPronosticoAsString () ) ;
      escritor.WriteEndElement () ;
      escritor.WriteWhitespace (
"\n" ) ;
    }
    escritor.WriteEndDocument () ;
    escritor.Close () ;
    Console.Write (
"Escrito resultado en quiniela.xml\n" ) ;
  }

  public static void
WriteEquipo (XmlTextWriter _escritor , string _quienJuega , string _dondeJuega ){
    _escritor.WriteStartElement (
"equipo" ) ;
    _escritor.WriteAttributeString (
"juega" , _dondeJuega) ;
    _escritor.WriteString (_quienJuega) ;
    _escritor.WriteEndElement () ;
  }

}

Antes de entrar al programa en sí, tendremos que comentar el formato que usamos para representar la Quiniela en XML, que es el siguiente:

<?xml version="1.0" encoding="us-ascii"?> <quiniela> <partido> <equipo juega="casa">Madrid</equipo> <equipo juega="fuera">Galatasaray</equipo> <resultado>1</resultado> </partido> <partido> <!-- Aquí el resto de los partidos --> </partido> </quiniela> [Árbol DOM de
     representación del documento XML quinielístico]

Usamos poquitas etiquetas, las estrictamente necesarias. El elemento raíz es quiniela, de ahí descienden (en el arbol DOM) una serie de partidos; y cada partido está compuesto por dos equipos, uno que juega en casa y otro fuera (ambos cualificados por el atributo juega). Finalmente, el pronóstico va en el elemento resultado. El resultado, en forma de árbol DOM pachangero, se muestra en la imagen.

El programa leerá un fichero de los creados anteriormente, creará un objeto por cada partido (aunque esto no es estrictamente necesario), y luego lo escribirá en un documento XML. Para eso se usa un objeto de tipo XmlTextWriter, que escribe sobre un fichero usando la codificación que le pasamos como segundo argumento (en este caso ASCII de toda la vida). El objeto está dentro del espacio de nombres System.Xml (y mucho ojito, que es X con mayúscula y ml con minúscula, que no sé porqué diablos, porque IO, por ejemplo, son las dos con mayúscula).

Pero los documentos XML tienen una estructura determinada; por eso usamos esto, si no, podríamos habérnoslo ahorrado y escribirlo como un texto con signos mayor y menor. Inicialmente, se especifica el formateo que se va a usar: Formatting.Indented. Eso no tiene mayor importancia, pero sí lo que viene luego. Todos los documentos XML tienen una etiqueta raíz, y dentro tiene elementos, que van adornados o no por atributos. Cada cosa tiene su correspondencia en métodos dentro de la clase XmlTextWriter; además, los elementos van emparejados, luego habrá métodos para comenzar y terminar unelemento. Eso se vé más o menos claro en el método WriteEquipo, que primero escribe el comienzo del elemento equipo, luego le pone un atributo y su valor correspondiente (WriteAttributeString), luego escribe el interior del elemento (WriteString), y finalmente, cierra el elemento.

El resto de los elementos funcionan de la misma forma; como van anidados, hay que tener en cuenta que deben acabar en orden contrario a como empiezan. También usamos otro método: WriteWhitespace, que escribe espacio en blanco.

Al final, como mandan los cánones, se cierra el escritor, con lo cual la estructura que se hubiera construido se escribe en disco.

El pro-blema con este pro-grama es que crea un documento XML, pero es para escribirlo en disco; si queremos hacer alguna cosa adicional con él, del tipo de cosas adicionales que se suelen hacer con los documentos XML (por ejemplo, a nuestro amiguete quinielero le han pedido que presente en la página web -interna- de la empresa los resultados de cada una de las quinielas), hay que construir el documento en memoria, y luego hacerle lo que sea. Eso es lo que se hace en el siguiente programa CreaQuinielaXML.cs:


using
System;
using
System.IO;
using
System.XML;

public class
Quiniela {
  public static void
Main ( string []args ){
    String JEnCasa = args[
0 ] ;
    String JFuera= args[
1 ] ;
    String Pronostico = args[
2 ] ;
    XmlDocument xmlDom =
new XmlDocument ();
    xmlDom.AppendChild (xmlDom.CreateElement (
"" , "quiniela" , "" ) ) ;
    XmlElement xmlRoot = xmlDom.DocumentElement ;
    XmlElement partido, jcasa, jfuera, pronos ;
    partido = xmlDom.CreateElement (
"" , "partido" , "" ) ;
    jcasa = xmlDom.CreateElement (
"" , "equipo" , "" ) ;
    jcasa.SetAttribute (
"juega" , "casa" ) ;
    XmlText texto;
    texto = xmlDom.CreateTextNode (arg[
0 ] ) ;
    jcasa.AppendChild (texto) ;
    partido.AppendChild (jcasa) ;
    jfuera = xmlDom.CreateElement (
"" , "equipo" , "" ) ;
    jfuera.SetAttribute (
"juega" , "fuera" ) ;
    texto = xmlDom.CreateTextNode (arg[
0 ] ) ;
    jfuera.AppendChild (texto) ;
    partido.AppendChild (jfuera) ;
    pronos = xmlDom.CreateElement (
"" , "pronostico" , "" ) ;
    texto = xmlDom.CreateTextNode (arg[
2 ] ) ;
    pronos.AppendChild (texto) ;
    partido.AppendChild (pronos) ;
    xmlRoot.AppendChild (partido) ;
    Console.WriteLine (xmlDom.InnerXml ) ;
  }

}

En este documento, que crea una quiniela con un sólo partido para simplificar la cosa, se trabaja construyendo un árbol DOM (Document Object Model). El funcionamiento es el siguiente: se van construyendo las hojas, y cuando están completas, se añaden a la rama correspondiente. Para crear las hojas se usa createElement, y para añadirlas se usa AppendChild. Cuando todo está construido, se le añade a la raíz del documento, que hemos creado originalmente. U séase, se planta. ¿Fácil, no?

Mirémoslo un poco más en detalle, fijándonos, por ejemplo, en como se crea el elemento de uno de los equipos. Primero, con jfuera = xmlDom.CreateElement( "", "equipo", "" ); se crea un elemento de tipo equipo; los parámetros que no se dan corresponden a prefijos y cosas que no vamos a usar aquí. La variable jfuera contiene ya un elemento, al cual, usando SetAttribute, le añadimos un atributo, usando lo que se le ha pasado por línea de órdenes. A continuación, con CreateTextNode le añadimos la "chicha", lo que está dentro de las etiquetas. Pero la "chicha" también es un nodo dentro de un documento XML, luego tenemos que añadirla como un hijo del elemento que ya habíamos creado anteriormente. Y una vez hecho eso, se añade el nodo ya creado completito a su nodo padre.

Un árbol así creado puede simplemente escribirse en un fichero, o en pantalla, o trabajar con él, aplicándole, por ejemplo, una transformación XSLT, y, por lo tanto, es preferible en muchos casos al modo de trabajar anterior; aunque evidentemente es más complicado. Se puede simplificar, sin embargo, creando métodos de una clase que creen automáticamente elementos para cada una de las variables de instancia de una clase.

Con esto, nuestro amigo está feliz y contento: puede meter la quiniela en el servidor web, y hacer lo que quiera con ella. ¿Pero qué puede hacer con las quinielas ya almacenadas? Pues leerlas con este programa (LeeQuinielaXML.cs):


using
quiniela.Partido;
using
System;
using
System.IO;
using
System.Xml;
public class
Quiniela {
  public static void
Main ( string []args ){
    XmlTextReader textReader =
new XmlTextReader ( "quiniela.xml" );
    textReader.Read () ;

    while
(textReader.Read () ){
      XmlNodeType textReader = textReader.NodeType ;
      Console.WriteLine (
"Leyendo {0}" , textReader.Name ) ;
      if
((nType == XmlNodeType.Element ) && (textReader.Name == "partido" )){
        String jcasa,jfuera ;
        jcasa = LeeEquipo(textReader,
"casa" ) ;
        jfuera = LeeEquipo(textReader,
"fuera" ) ;
        textReader.Read () ;
        textReader.Read () ;

        string
pronostico = "" ;
        if
(textReader.Name == "resultado" ){
          textReader.Read () ;
          pronostico = textReader.Value ;
        }
        Partido estePartido =
new Partido (jcasa, jfuera, pronostico);
        Console.WriteLine (estePartido.getAsString () ) ;
      }
    }
  }

  public static string
LeeEquipo (XmlTextReader _reader , string _donde ){
    _reader.Read () ;
    _reader.Read () ;
    String juega =
"" ;
    if
((_reader.GetAttribute ( "juega" ) == _donde) && (_reader.Name == "equipo" )){
      _reader.Read () ;
      juega = _reader.Value ;
    }
    _reader.Read () ;

    return
juega;
  }

}

Este programa es más complicado de lo que parece, porque la clase que se usa, XmlTextReader, lo es. Esta clase crea una especie de stream, del cual se va leyendo, pero no está muy clara cuál es la "unidad de lectura". En cualquier caso, experimentando, nuestro amiguete dio con esta forma, en la cual hay muchos Read que aparentemente no hacen nada, pero son necesarios.

Después de inicialicar el lector, se va "leyendo de él". El lector es a la vez un cursor, pues apunta al nodo del documento XML que se va leyendo en ese momento; por eso se usa para extraer el tipo de nodo. Si el tipo de nodo es Element y además el nombre del elemento es partido, entonces se empieza a leer para hallar los equipos y el pronóstico; para ello se usa una subrutina. Para detectar que estamos en el nodo correcto se usa GetAttribute y el nombre del elemento en el que estamos situados. Con el pronostico pasa igual: se mira si el nombre del nodo es resultado, y si es así, se mete en la variable correspondiente. Suena caótico, lo sé, pero es que es un poco caótico. Probablemente haya mejores formas de leer un documento XML.

Finalmente, se crea un objeto de tipo Partido, y se escribe en la consola. Qué menos que hacer eso. Habría que meterlo quizás en un array, o en una BD, o algo de eso. Pero eso se deja como ejercicio para el lector.

Ejercicios
1. Hacer un programa que recoja los datos de las encuestas realizadas en los anteriores ejercicios, y lo escriba en XML. 2. Hacer un programa que extraiga los contenidos de un directorio, y los escriba en XML usando un conjunto de elementos diseñados por uno. No hace falta que use todos los elementos de un directorio, solo algunos de ellos.

Esta es la bibliografía que ha aparecido hasta el momento sobre el tema, especialmente en castellano

Evidentemente, no hay ningún libro que trate específicamente de este apartado, pero sí libros generales de C# que lo tratan con suficiente extensión. Entre ellos Programming C#, de Jesse Liberty y Valerie Quercia, que se editará en Junio 2003. Actualmente está también disponible la segunda edición. Y en cuanto a la primera edición, está disponible en Safari, siempre que se tenga contratado ese servicio.

Este tutorial es (c) Juan J. Merelo Guervós. Se podrá reproducir en todo o en parte, siempre que se mantenga un enlace al sitio original.