diumenge, 29 de maig del 2011

Parseig d'XML amb C# i DOM (Mono)

Una de les guerres religioses més interessants que hi han hagut en els darrers temps en el món del programari lliure ha estat entre els partidaris i detractors de Mono.

Mono és una implementació lliure de .Net per a màquines GNU/Linux basada en l'estàndar ECMA. La batalla al voltant d'aques projecte és perquè part de les eines que s'han implementat podrien no respectar les llicències de Microsoft. Es tem que, en un moment donat, els de Redmond pordrien denunciar aquesta possible violació de llicències i provocar que tot allò que estigues desenvolupat a partir d'aquest programari no lliure de Mono quedés en una situació difícil.


Els defensors de Mono diuen que les parts conflictives són molt poques i estan ben localitzades i que programar sobre Mono no presenta perill.

No prendré partit. En tot cas, des del meu punt de vista, Mono ofereix una plataforma  alternativa als desenvolupadors de .Net. Una plataforma basada en programari i sistemes operatius lliures, amb l'extraordinari estalvi de cost de llicències que això suposa.

Per mi, doncs, Mono és una plataforma a tenir en compte pels desenvolupadors. I no només. Amb Mono sobre Linux i Apache es poden executar aplicacions  CMS com dotNetNuke, o  MojoPortal. EL trio Linux,Apache,Mono ofereix, doncs, una alternativa per al hosting amb ASP.

Mono ens porporciona eines tan interessants com els compiladors de C# i  VB.NET que poden compilar a Linux nadiu però també al bytecode del CLI i, per tant, poden produir executables aptes per Windows.

En aquest post presento com fer servir un fitxer XML com a fitxer de configuració.  NET no té, com Java, una classe per a fitxers de properties de Unix, ni fitxers .ini de Windows. És dir, fitxers amb files  nom=valor. La filosofia NET recomana fer servir XML per a les configuracions.

NET és eficient amb l'XML. Per als programadors de NET  provinents de Java l'anàlisi de fitxers XML té dos noms principals associats: DOM i SAX.

De forma molt resumida: DOM permet analitzar fitxers més aviat petits i es basa en carregar el fitxer en memòria i exposar-lo a les aplicacions com un arbre d'objectes que ens representen els elements i els atributs.

SAX, per la seva banda, és un parser que processa  un flux XML i que respon, amb funcions de callback, quan es produeixen determinats esdeveniments, per exemple l'aparició d'una etiqueta concreta o una ordre de processament. SAX és lleuger i  adequat per al processat de grans fluxos, o grans fitxers d'XML.

Doncs bé. L'API estàndar de SAX no està al framework de .NET. Existeix, això sí, una API pròpia que és, conceptualment, equivalent a SAX (diguem que el parseig basat en XmlTextReader). I també es pot trobar la implementació de SAX per a .NET, com una descàrrega externa.

En canvi, l'API estàndar DOM està implementada al framework de .NET. El que faré serà aprofitar l'API DOM de NET per a fer servir un fitxer  XML com a fitxer de configuració.

El fitxer XML és aquest:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE claus> 
<claus>
<clau nom="Nom1" valor="Valor1" />
<clau nom="Nom2" valor="Valor2" />
</claus>

Primer de tot, amb el MonoDevelop (instal·lable des del Centre de programari de l'Ubuntu) crearé una nova Solució del tipus aplicació de consola amb  C#.
Afegeixo la referència System.Xml i creo dues classes C#

KeyValuePair és, com el seu nom indica, una estructura de dades clau-valor

using System;
using System.Collections.Generic;
using System.Text;

namespace provaproperties
{
    class KeyValuePair
    {
        string sKey;
        string sValue;

        public KeyValuePair(string sKey, string sValue)
        {
            this.sKey = sKey;
            this.sValue = sValue;
        }

        public string getKey()
        {
            return sKey;
        }

        public string getValue()
        {
            return sValue;
        }

    } // de la class
} // del namespace

La classe Config, carrega un fitxer XML de configuració, l'analitza amb XML i el fa servir per carregar parells nom valor en una classe de col·lecció ArrayList. A més, proporciona un mètode per obtenir un valor de la llista a partir d'una clau.

Un incís: el que acabo d'implementar aquí és un diccionari. NET proporciona la classe Dictionary a System.Collections.Generic. L'objectiu del post era presentar l'ús de DOM, no implementar un diccionari. Si el que ens cal és un diccionari, la millor opció, evidentment, és fer  servir la classe del framework.

La classe config:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Text;

namespace provaproperties
{
    class Config
    {
        ArrayList arrKeyValuePairs = null;
       
        public static void Main (string[] args)
        {
            Console.WriteLine ("Anàlisi de fitxer de configuració");
            Config cf = new Config("./config.xml");
            Console.WriteLine("clau: Nom1; valor: {0}", cf.getValue("Nom1"));
            Console.WriteLine("clau: Nom2; valor: {0}", cf.getValue("Nom2"));
            Console.WriteLine("clau: Nom3; valor: {0}", cf.getValue("Nom3"));   
        }
       
       
        public Config(string sXmlConfigFile)
        {
            arrKeyValuePairs = new ArrayList();
           
            /* XML de configuracions
            * <?xml version="1.0" encoding="utf-8" ?>
            * <!DOCTYPE claus>
            * <claus>
            *   <clau nom="Nom1" valor="Valor1" />
            *   <clau nom="Nom2" valor="Valor2" />
            *   ...
            * </claus>
            */

            string sClau = "";
            string sValor = "";
           
            // DOM parsing
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(sXmlConfigFile);
            XmlNodeList xnl = xdoc.GetElementsByTagName("clau");
            foreach (XmlNode node in xnl)
            {
                XmlAttributeCollection attrColl = node.Attributes;
                foreach( XmlAttribute attr in attrColl)
                {
                    string sName = attr.Name;
                    string sValue = attr.Value;

                    if (sName == "nom")
                    {
                        sClau = sValue;
                    }

                    if (sName == "valor")
                    {
                        sValor = sValue;
                    }
                }

                // afegeix les claus a l'Array
                arrKeyValuePairs.Add(new KeyValuePair(sClau, sValor));
            }
        }


        public string getValue(string sClau)
        {
            // si no el troba, torna "".
           
            string sValue = "";

            foreach (KeyValuePair item in arrKeyValuePairs)
            {
                if (item.getKey() == sClau)
                {
                    sValue = item.getValue();
                    break;
                }
            }
           
            return sValue;
        }
       
    } // de la class
}  // del namespace


I compilem. L'execució del programa proporciona l'efecte esperat:

albert@atenea:~/wk-mono/prova-properties/prova-properties/bin/Debug$ ./prova-properties.exe
Anàlisi de fitxer de configuració
clau: Nom1; valor: Valor1
clau: Nom2; valor: Valor2 
clau: Nom3; valor:

Encara un altre incís: si la intenció és fer servir l'XML com a fitxer de configuració mitjançant DOM, aleshores, el fet de carregar una ArrayList amb els parells nom valor resulta, si més no, redundant.


Una solució sense redundàncies seria crear un mètode que obtingués el valor corresponent a la clau cercada directament a partir del Document XML parsejat. Per exemple, una cosa com la següent. Un detall interessant, la doble iteració sobre els atributs. És necessària perquè l'ordre de la col·lecció d'atributs no està determinat i, si fes una sola passada, podria ser que el valor aparegués abans del nom, i no sabria identificar-lo. Vet aquí el codi:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Text;

namespace provaproperties
{
    class XmlConfig
    {
        XmlDocument xdoc = null;
       
        public static void Main (string[] args)
        {
            Console.WriteLine ("Anàlisi de fitxer de configuració - millorat");
            Config cf = new Config("./config.xml");
            Console.WriteLine("clau: Nom1; valor: {0}", cf.getValue("Nom1"));
            Console.WriteLine("clau: Nom2; valor: {0}", cf.getValue("Nom2"));
            Console.WriteLine("clau: Nom3; valor: {0}", cf.getValue("Nom3"));   
        }
       
       
        public XmlConfig(string sXmlConfigFile)
        {
           
            /* XML de configuracions
            * <?xml version="1.0" encoding="utf-8" ?>
            * <!DOCTYPE claus>
            * <claus>
            *   <clau nom="Nom1" valor="Valor1" />
            *   <clau nom="Nom2" valor="Valor2" />
            *   ...
            * </claus>
            */
           
            // DOM parsing
            XmlDocument xdoc = new XmlDocument();
            xdoc.Load(sXmlConfigFile);
        }

        public string getValue(string sClau)
        {
            // si no el troba, torna "".
            string sFoundValue = "";

            // obté la llista de nodes
            XmlNodeList xnl = xdoc.GetElementsByTagName("clau");
           
            // itera pels nodes
            foreach (XmlNode node in xnl)
            {
                // obté els atributs del node
                XmlAttributeCollection attrColl = node.Attributes;
                Boolean boolTrobat = false;
               
                // itera pels atributs del node
                foreach( XmlAttribute attr in attrColl)
                {
                    // nom de l'atribut
                    string sName = attr.Name;
                    // valor de l'atribut
                    string sValue = attr.Value;

                    // si el nom de l'atribut és nom
                    if (sName == "nom")
                    {
                        // aleshores, si el valor coincideix amb la clau buscada
                        if (sValue == sClau)
                        {
                            // és que l'ha trobat
                            boolTrobat = true;
                        }
                    }
                }
                   
                // si l'ha trobat, aleshores busca el valor
                if (boolTrobat) {
                    // itera pels atributs del node un altre cop
                    foreach( XmlAttribute attr in attrColl)
                    {
                        // nom de l'atribut
                        string sName = attr.Name;
                        // valor de l'atribut
                        string sValue = attr.Value;
   
                        // si el nom de l'atribut és valor
                        if (sName == "valor")
                        {
                            // aleshores, ja el tenim
                            sFoundValue = sValue;
                            break;
                       
                        }
                    }
                }   
            } // del foreach
           
            return sFoundValue;
       
        } // del mètode       
    } // de la class
}  // del namespace


Com abans, compilem i executem. El resultat és l'esperat:

albert@atenea:~/wk-mono/prova-properties/prova-properties/bin/Debug$ ./prova-properties.exe
Anàlisi de fitxer de configuració - millorat
clau: Nom1; valor: Valor1
clau: Nom2; valor: Valor2
clau: Nom3; valor:

Cap comentari:

Publica un comentari a l'entrada