HTML5. WebSockets amb Jetty

Continuant amb l’exploració de l’HTML5, avui parlo dels WebSockets. I per a il·lustrar-lo, faré un petit exercici amb websockets fent servir el servidor java Jetty.

La idea de l’exemple l’he tret del projecte http://code.google.com/p/phpwebsocket/. El client html és pràcticament idèntic. La part del servidor, en el meu exemple, l’he implementada amb Java; estant en PHP en l’original.

Els WebSockets permeten realitzar la comunicació bidireccional i full-duplex sobre un canal TCP, entre la pàgina web i el servidor de forma molt més eficient que amb l’esquema de petició-resposta de l’HTML, o que la comunicació amb AJAX o, més explí­citament, mitjançant Comet.

El protocol WebSocket es descriu a la RFC6455.

A dia d’avui, aquest protocol es troba implementat per les darreres versions dels principals navegadors:  Chrome, Internet Explorer, Firefox i Safari.

A més, el servidor ha de soportar també els WebSockets de servidor.

En si mateix el protocol recorda una crida HTTP, però no ho és:

Exemple de petició des del client:

GET /mychat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com

Exemple de resposta del servidor:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

Des del punt de vista del desenvolupament parlem, doncs, de clients i servidors de WebSockets.
HTML5 ofereix una API (normalitzada pel W3C) per a desenvolupar clients d’aplicacions basades en WebSocket.

Per la banda del servidor, els llenguatges més utlitzats, com Java o php aporten classes, mètodes i funcions per a crear i utilitzar websockets de servidor.

Sobre servidors Java: jWebSocket, Apache Tomcat i Jetty també tenen implementacions de sockets de servidor.
Sobre servidors PHP: phpWebSockets

Podem trobar implementacions de servidor WebSocket amb altres llenguatges. Per exemple amb Python una cerca ens proporciona: pywebsocket, o WebSocket for Python (ws4py).

Per a fer un experiment senzill utilitzaré el servidor de java servlets Jetty (versió 8) que proporciona una implementació java de WebSockets de servidor. Podeu revisar aquest article: Jetty WebSocket Server, i aquest altre: Jetty/Feature/WebSockets.
Per descomptat, el javadoc: http://download.eclipse.org/jetty/stable-8/apidocs/

Per a provar els WebSockets he creat un petit projecte amb Eclipse, amb la següent estructura de carpetes:

A la carpeta src hi creo el package java amb la següent classe: com.stsoftlliure.proves.html5.websockets.ProvaWebSocket

Fitxer ProvaWebSocket.java:

package com.stsoftlliure.proves.html5.websockets;

import java.io.IOException;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
import org.eclipse.jetty.util.log.Log;
public class ProvaWebSocket extends WebSocketServlet  {

   private static final long serialVersionUID = 8442112605629206192L;


    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
    throws ServletException ,IOException  {
      response.getOutputStream().println("aquesta és la resposta");
      Log.info("[doGet] No fa res");
    }

    public WebSocket doWebSocketConnect(HttpServletRequest request,
                                        String protocol) {
      Log.info("[doWebSocketConnect] entra a doWebSocketConnect");
      return new ProvaServerWebSocket();
    }

    // inner class que implementa la interface WebSocket
    class ProvaServerWebSocket implements WebSocket.OnTextMessage {
      // la connexió
      Connection wsConn;
      Date dataInici;

      public ProvaServerWebSocket() {
         dataInici = new Date();
      }

      @Override
      public void onClose(int arg0, String arg1) {
         // TODO Auto-generated method stub
         Log.info("[onClose] arg0:" + arg0 + "; arg1: " + arg1);
         wsConn.close(arg0, arg1);
      }

      @Override
      public void onOpen(Connection wsConn) {
         this.wsConn = wsConn;
         try {
            Log.info("[onOpen] wsConn:" + wsConn);
            wsConn.sendMessage("Estic connectat!");
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.debug("Error: " + e.toString());
         }
      }

      @Override
      public void onMessage(String sMessage) {
         // TODO Auto-generated method stub
         Log.info("[onMessage] Missatge rebut: " + sMessage);
            // fa el tractament:
         // to do
         // help, hola, nom, edat, data, adeu
 
         try {
            if (sMessage.trim().equalsIgnoreCase("help")) {
               wsConn.sendMessage("ordres acceptades: help, hola, nom, edat, data, adeu");
            }

            if (sMessage.trim().equalsIgnoreCase("hola")) {
               wsConn.sendMessage("Hola! benvingut al servidor WebSocket!");
            }

            if (sMessage.trim().equalsIgnoreCase("nom")) {
               wsConn.sendMessage("Jo em dic ProvaWebSocket.class");
            }

            if (sMessage.trim().equalsIgnoreCase("edat")) {
               wsConn.sendMessage("En funcionament des de: " + dataInici);
            }

            if (sMessage.trim().equalsIgnoreCase("data")) {
               wsConn.sendMessage("ara són les: " + (new Date()));
            }

            if (sMessage.trim().equalsIgnoreCase("adeu")) {
               wsConn.sendMessage("Adéu siau!");
               wsConn.close();
            }
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.debug("Error: " + e.toString());
         }
      }
   }
}

Algunes remarques a la classe anterior:

– La classe és un servlet del tipus nou WebSocketServlet.
– Els WebSocketServlet implementen el doGet. El puc invocar directament i obtinc una resposta HTML. Però la funcionalitat apareix amb el nou mètode doWebSocketConnect. A doWebSocketConnect és on treballa el socket de servidor.
-Quan es rep una nova connexió, el mètode doWebSocketConnect el que fa és instanciar la inner class ProvaServerWebSocket, que implementa la interface WebSocket.OnTextMessage.
– La interface WebSocket té altres subinterfaces, a més de la OnTextMessage, per a tractar altres tipus de connexions. Les subinterfaces de WebSocket són: WebSocket.OnBinaryMessage, WebSocket.OnControl, WebSocket.OnFrame, WebSocket.OnTextMessage.
– en la connexió onOpen, obtinc l’objecte Connection wsConn. Que serà propiament el canal full duplex entre servidor i client.
– El tractament d’un missatge rebut el faig en implementar el mètode public void onMessage(String sMessage). sMessage és el missatge rebut des del client. Les respostes al client s’envien mitjançant l’objecte Connection wsConn obtingut en l’event onOpen. En aquest exemple, simplement analitzo quin missatge rebo i, depenent del missatge, envio una o altre resposta al client.
– A la sortida onClose, tanco la connexió

Senzill i net, oi?

L’altre part és la pàgina del client. Una pàgina HTML5 que fa servir websockets ha d’implementar els mètodes que responen als esdeveniments de connexió, alliberament, recepció i enviament de missatges al servidor. Vet aquí la pàgina HTML que dialoga amb el servidor:

Fitxer prova.html

<html>
<head>
<title>WebSocket</title>

<style>
 html,body{font:normal 0.9em arial,helvetica;}
 #log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}
 #msg {width:330px;}
</style>

<script>
var socket;

function init(){
  var host = "ws://localhost:8080/websockets2/servlet/WebSocket";
  try{
    socket = new WebSocket(host);
    log('WebSocket - status '+socket.readyState);
    socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
    socket.onmessage = function(msg){ log("Received: "+msg.data); };
    socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
  }
  catch(ex){ log(ex); }
  $("msg").focus();
}

function send(){
  var txt,msg;
  txt = $("msg");
  msg = txt.value;
  if(!msg){ alert("El missatge no pot ser nul"); return; }
  txt.value="";
  txt.focus();
  try{ socket.send(msg); log('Enviat: '+msg); } catch(ex){ log(ex); }
}

function quit(){
  log("Adéu!");
  socket.close();
  socket=null;
}

// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="<br>"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</script>

</head>
<body onload="init()">
 <h3>WebSocket</h3>
 <div id="log"></div>
 <input id="msg" type="textbox" onkeypress="onkey(event)"/>
 <button onclick="send()">Send</button>
 <button onclick="quit()">Quit</button>
 <div>Commands: help, hola, nom, edat, data, adeu</div>
</body>
</html>

Alguns comentaris: com es veu, la pàgina és senzilla, la clau del funcionament està al mètode init

function init(){
  var host = "ws://localhost:8080/websockets2/servlet/WebSocket";
  try{
    socket = new WebSocket(host);
    log('WebSocket - status '+socket.readyState);
    socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
    socket.onmessage = function(msg){ log("Received: "+msg.data); };
    socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
  }
  catch(ex){ log(ex); }
  $("msg").focus();
}

El qual crea l’objecte socket, el connecta al host, adonem-nos del protocol “ws”; i enllaça els esdeveniments del socket: onopen, onmessage, onclose amb les funcions que els tracten.

L’enviament de missatges al servidor es realitza amb la funció send, que invoca al mètode send de l’objecte socket.

function send(){
  var txt,msg;
  txt = $("msg");
  msg = txt.value;
  if(!msg){ alert("El missatge no pot ser nul"); return; }
  txt.value="";
  txt.focus();
  try{ socket.send(msg); log('Enviat: '+msg); } catch(ex){ log(ex); }
}

Amb l’anterior ja tinc tot el codi. Poso la pàgina prova.html a la carpeta webcontent del projecte Eclipse. A més, en la carpeta webcontent també hi posaré la carpeta WEB-INF, amb una subcarpeta classes buida, la carpeta lib amb les llibreries que em calen i el web.xml.

En resum, a webcontent hi trobo:

webcontent/
    prova.html
    WEB-INF/
        classes/
        lib/
            jetty-util-8.1.2.v20120308.jar
            jetty-websocket-8.1.2.v20120308.jar
        web.xml

Les llibreries a la carpeta lib corresponen a la versió 8 del Jetty i es poden trobar a la seva carpeta lib. He vist que cal incloure-les al war. Em pensava que es referenciarien automàticament al fer deploy al Jetty, però no ha estat així.

El web.xml és força estàndar:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name>tailor</display-name>
	<servlet>
		<servlet-name>WebSocket</servlet-name>
		<servlet-class>com.stsoftlliure.proves.html5.websockets.ProvaWebSocket</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>WebSocket</servlet-name>
		<url-pattern>/servlet/*</url-pattern>
	</servlet-mapping>

	<welcome-file-list>
		<welcome-file>prova.html</welcome-file>
	</welcome-file-list>
</web-app>

Si de cas, remarcar el mapeig de /servlet/* a WebSocket. Aquesta és l’explicació de
var host = “ws://localhost:8080/websockets2/servlet/WebSocket”;
a la funció init.

Amb Jetty, a més, m’ha calgut establir el “context.” Al projecte, el fitxer de context l’he guardat a la carpeta homònima. És el següent fitxer.

websockets2.xml:

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="contextPath">/websockets2</Set>
  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/websockets2.war</Set>
</Configure>

Compte amb els salts de línia. El fitxer XML de context potser no queda “bonic” tal com està, però us podeu trobar amb problemes estranys si afegiu salts de línia.

Amb el fitxer anterior li estem dient quin és el war que ha de desplegar i quin nom tindrà el context. Amb aquest fitxer de context acabem de completar l’explicació del

var host = “ws://localhost:8080/websockets2/servlet/WebSocket”;

Ara és evident:

ws:// – Protocol websocket. Amb websockets segurs, serà wss://
localhost:8080 – Host i port del servidor websocket
websockets2 – El context, o l’aplicació web.
servlet/WebSocket – el Servlet servidor de WebSocket

Finalment, per automatitzar el muntatge i ddesplegament al servidor jetty local de l’apliació, faig servir el següent build.xml d’ant:

<project name="websocket" default="deploy" basedir=".">
  <description>
  proves amb websockets 2
  </description>

  <!-- estableix propietats globals -->
  <property name="src" location="src"/>
  <property name="bin" location="bin"/>
  <property name="war" location="war"/>
  <property name="webcontent" location="webcontent" />
  <property name="context" location="context" />
  <property name="build" location="build"/>
  <property name="lib" location="/home/albert/jetty/lib" />
  <property name="deploy" location="/home/albert/jetty/webapps" />
  <property name="deploy-context" location="/home/albert/jetty/contexts" />


  <target name="init">
    <!-- time stap -->
    <tstamp/>
    <!-- Crea el directory bin utilitzat en la compilació -->
    <mkdir dir="${bin}"/>
  </target>


  <target name="compile" depends="init" description="compila els fitxers font" >
    <!-- Compila les fonts java de ${src} en ${build} -->
    <javac srcdir="${src}" destdir="${bin}">
      <classpath>
       <pathelement location="${lib}/jetty-websocket-8.1.2.v20120308.jar"/>
       <pathelement location="${lib}/servlet-api-3.0.jar"/>
       <pathelement location="${lib}/jetty-util-8.1.2.v20120308.jar"/>
      </classpath>
   </javac>
  </target>

  <target name="build" depends="compile" description="genera el build">
    <!-- Crea el directori de distribució -->
    <delete dir="${build}" />
    <mkdir dir="${build}"/>

    <!--  copia webcontent a build -->
    <copy todir="${build}" >
        <fileset dir="${webcontent}" />
    </copy>
    
    <!--  copia bin a classes -->
    <copy todir="${build}/WEB-INF/classes">
        <fileset dir="${bin}" />
    </copy>
  </target>
     
  <target name="war" depends="build" description="genera el war">   
    <delete file="${war}/websockets2.war" />
    <jar jarfile="${war}/websockets2.war" basedir="${build}"> 
      <include name="**/*"/>
    </jar>
  </target>
  	
  <target name="deploy" depends="war" description="deploy del war i del context">
    <delete file="${deploy}/websockets2.war" />
    <delete file="${deploy-context}/websockets2.xml" />
  	<copy file="${war}/websockets2.war" todir="${deploy}" />
  	<copy file="${context}/websockets2.xml" todir="${deploy-context}" /> 
  </target>

</project>

Aleshores, ses d’Eclipse puc executar el build.xml que compila la classe, construeix el war i el copia a la carpeta webapps del meu Jetty local.

Per provar l’aplicació només en cal engegar el Jetty i obrir el navegador amb suport de websockets (en el meu cas, el Chrome) i apuntar-lo a l’adreça http://localhost:8080/websockets2

I el resultat és el següent.

I, efectivament, puc mantindre el diàleg amb el servidor:

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: