Generar un fichero XML con XDocument


La tecnología LINQ tiene como principal atractivo su versatilidad: es capaz de realizar proyecciones y filtrado sobre cualquier colección iterable, así como de generar colecciones de objetos “al vuelo” de forma sencilla, tal y como vimos en el artículo anterior.

Hoy aprenderemos a hacer uso de uno de los grandes avances que Microsoft ha proporcionado, desde mi punto de vista, al mundo de la programación .NET: el espacio de nombres System.Xml.Linq. Para ello crearemos una pequeña función que, haciendo uso de LINQ y Reflection, recorra todos los elementos de un objeto y genere un fichero XML con sus metadatos.

Supongo que todos sabremos qué es un fichero XML y cuál es su estructura, pero no viene de más realizar una pequeña introducción a este tipo de documentos antes de aprender a bucear en su información y a generar ficheros de este modo.

El fichero XML

Un fichero XML (eXtensible Markup Language) es un fichero de marcas (como HTML) completamente personalizable que permite almacenar datos de forma esquemática y ordenada. Los elementos que nos interesan para entender este ejemplo son los siguientes:

  • Documento: engloba el fichero XML en sí. Se compone de una cabecera o prólogo y de un elemento o nodo raíz, del que colgarán (estarán anidados dentro de él) el resto de elementos del documento. En la imagen que puede verse más abajo, el elemento raíz sería “configuración”.
  • Elemento o nodo: elemento principal de un fichero XML. Permite definir atributos y anidar otros elementos dentro de ellos. En la imagen inferior, los elementos del documento serían configuration, startup (que sería hijo directo de configuration) y supportedRuntime (que sería hijo directo de startup y nieto de configuration).
  • Atributo: asocia propiedades o características a un elemento, y se compone por un par clave-valor. En el caso inferior podemos ver algunos de los atributos, como “versión”, que será un atributo del elemento supportedRuntime y cuyo valor será “v4.0”.

XML

El espacio de nombres System.Xml.Linq

Antes de la aparición de este espacio de nombres, allá por el Framework 2.0, manejar ficheros XML era una tarea tediosa, engorrosa y complicada. Sin embargo, la aparición de LINQ simplificó enormemente esta tarea, especialmente a nivel conceptual, ya que acercó el sistema de construcción de un fichero XML real a la forma de construirlo a nivel de código. Así, el documento anterior podría modelarse gráficamente de la siguiente forma:

LINQ provee de elementos para simbolizar cada uno de estos elementos:

  • XDocument: simbolizará el documento XML, que contendrá un nodo raíz.
  • XElement: simbolizará un nodo o elemento, que puede a su vez albergar más elementos (y así recursivamente)
  • XAttribute: simbolizará un atributo, compuesto por un nombre (clave) y un valor. No almacenará más elementos de forma anidada.

Ahora que conocemos esta estructura, podríamos modelar el fichero del siguiente modo gracias a estos elementos:

  • XDocument (documento XML)
    • XElement configuration. Contiene un nodo hijo.
      • XElement startup. Contiene un nodo hijo.
        • XElement supportedRuntime. Contiene dos atributos
          • XAttribute versión.
          • XAttribute sku

Construyendo un documento XML

La forma de construir esta estructura es tan sencilla como “traducir” el esquema anterior a código de la siguiente forma:


                XDocument documentoXml = new XDocument(
                    new XElement("configuration",
                        new XElement("startup",
                            new XElement("supportedRuntime",
                                new XAttribute("version", "v4.0"),
                                new XAttribute("sku", ".NETFramework,Version=v4.5")
                            )
                        )
                    )
                );
                return documentoXml.ToString();

Como vemos, la propia estructura del fichero XML “pide” ir generando los elementos del mismo modo que lo haríamos si escribiéramos el fichero XML de forma manual. Minipunto para Microsoft por ahorrarnos horas y horas de trabajo buceando en los entresijos de los generadores de este tipo de documentos. Una vistazo rápido al fichero generado nos mostrará la dulce realidad:

Si el lector ha estado atento podrá advertir que el fichero XML no está del todo completo: falta la declaración (la cabecera del fichero) en la que se indica la versión del fichero y el tipo de codificación. Para subsanar este error tenemos otro elemento, XDeclaration, que nos permite indicar esos datos si se lo pasamos como primer parámetro al documento, de la siguiente forma:


                XDocument documentoXml = new XDocument(
                    new XDeclaration("1.0", "utf-8", null),
                    new XElement("configuration",
                        new XElement("startup",
                            new XElement("supportedRuntime",
                                new XAttribute("version", "v4.0"),
                                new XAttribute("sku", ".NETFramework,Version=v4.5")
                            )
                        )
                    )
                );

Por desgracia el método toString() de XDocument no producirá una salida que incluya la cabecera, ya que se trata de los propios metadatos del fichero XML. Para que se manifiesten no tendremos otro remedio que generar un fichero XML físico. O en su defecto, “emular” una escritura y hacer uso del flujo de salida para acceder al resultado final. Haremos uso, por lo tanto, de un objeto de la clase StringWriter para guardar el documento que, a continuación, recuperaremos en su forma de cadena de texto. El método completo, por lo tanto, quedaría del siguiente modo:


                XDocument documentoXml = new XDocument(
                    new XDeclaration("1.0", "utf-8", null),
                    new XElement("configuration",
                        new XElement("startup",
                            new XElement("supportedRuntime",
                                new XAttribute("version", "v4.0"),
                                new XAttribute("sku", ".NETFramework,Version=v4.5")
                            )
                        )
                    )
                );

                StringWriter writer = new StringWriter();
                documentoXml.Save(writer);

                return writer.GetStringBuilder().ToString();

Ejecutemos nuevamente y veamos el resultado:

StringWriter y Encoding

Un momento… la cabecera del fichero XML indica que la codificación es utf-16, pero yo le he indicado que era utf-8. ¿Por qué ha ocurrido esto? La respuesta es simple: el StringWriter ha codificado el fichero en utf-16. El motivo es porque .NET almacena las cadenas de forma interna en UTF-16. Además SQL Server presupone que, cuando trabaja con ficheros XML, éstos estarán codificados en utf-16.

No obstante, en ocasiones es posible (y probable) que tengamos que cambiar nuestra codificación, generalmente a utf-8. En este caso, Microsoft no nos deja otra alternativa que “puentear” la clase StringWriter y asignarle manualmente la codificación mediante la creación de una subclase. StringWriter posee una propiedad Encoding que permite recuperar la codificación, pero por desgracia se trata de una propiedad de sólo lectura (mal, Microsoft, mal), por lo que nos tocará añadir una nueva clase que pueda solventar este “pequeño descuido”.


    public class StringWriterEncode : StringWriter
    {
        // Añadimos un atributo que almacenara la nueva codificacion
        private Encoding encoding;

        // Creamos un nuevo constructor que permita asociar una nueva codificacion
        public StringWriterEncode(Encoding e) : base()
        {
            this.encoding = e;
        }

        // Sobrecargamos el getter que devuelve la codificacion
        public override Encoding Encoding
        {
            get
            {
                return encoding;
            }
        }

        // Añadimos un nuevo getter que permita recuperar la codificacion por defecto
        public Encoding DefaultEncoding
        {
            get
            {
                return base.Encoding;
            }

        }
    }

Hecho esto, cambiamos el StringWriter por nuestro recién creado StringWriterEncode y ejecutamos nuevamente el método.


    StringWriter writer = new StringWriterEncode(Encoding.UTF8);

Y el resultado, por fin, será el esperado:

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s