|
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 |
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 |
|---|
|
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:
FileMode.Open, en
este caso; hay otros modos de apertura que incluyen Append para añadir y OpenOrCreate, que crearía un fichero si no existe,ReadWrite),FileShare.Read indica
una especie de cerrojo en el fichero, que permite a otros
procesos abrirlo para leer, pero no para escribir. Hay otros modos de compartir, por ejemplo, None no permitiría a ningún otro recurso usar el fichero abiertoEs 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 |
|---|
|
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 |
|---|
|
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>
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
System.IO.File. En el mismo sitio web hay otra serie de tutoriales. El sitio de Mono en castellano incluye también otros recursos, como una lista de correo de ayuda sobre Mono y otros proyectos específicos.System.IO.Directory y trabajo
básico con ficheros en C#.
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.