Alters

Aventuras de DoHITB: DoHITB vs. JavaScript (II)

Buenas!

Seguimos con las aventuras de mi "alter ego", DoHITB... ¿preparados?

En esta entrada os contaré cómo me las apañé para cargar el XML desde JavaScript, y cómo tratarlo línea a línea.

Cargar el documento:

Para ello usamos algo muy similar a AJAX (en realidad, es algo así como SXWJ)

¿Qué?

Claro...

AJAX = Asyncronus JavaScript And XML (JavaScript asíncrono y XML - es decir, se trata JavaScript Y XML de manera asíncrona)

por otra parte...

SXWJ = Syncronus XML With XML (XML sincrónico con JavaScript - es decir, se trata XML desde un Javascript, todo de manera sincrónica)

Obviamente, el "SXWJ" me lo acabo de sacar de la manga... jejeje. Era para dejar clara la diferencia (sutil) entre cargar un XML y usar AJAX.

Bromas aparte. Una búsqueda rápida por Internet revela la función básica para cargar un XML:

function LoadXML(file){
xmlDoc = undefined;

try{
if(document.all){
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
}else{
xmlDoc = document.implementation.createDocument("","", null);
}
xmlDoc.async = false;
xmlDoc.load(file);     
}catch(e){
try{
xmlhttp = new window.XMLHttpRequest();
xmlhttp.open("GET", file, false);
xmlhttp.send(null);
xmlDoc = xmlhttp.responseXML.documentElement;
GlobalDoc = xmlDoc;
return;
}catch(e){
GlobalDoc = undefined;
}
}

GlobalDoc = xmlDoc;
}

Explicamos rápido:

definimos una variable local (xmlDoc), y comprobamos el navegador (si entra en el "if(document.all)" es Internet Explorer), en caso contrario es otro navegador (excepto Chrome y derivados).

Si en algún momento falla (es Chrome o derivado, o bien hay algún error) iniciamos el tratamiento para Chrome.

Si fallara algo, es un error grave.

Por último, fijaos que hay una variable "GlobalDoc", que estará definida en el JavaScript como "var GlobalDoc", es decir, en un ámbito global (ya que xmlDoc es local), para que podamos acceder a ella desde cualquier punto del documento.

Por último, el parámetro "file" es la ruta del documento (tipo URL) donde está el XML.

¿Qué puede causar fallos en esta función:
  1. El fichero no está en ninguna URL
  2. La URL que pasamos es inaccesible
  3. La URL que pasamos no existe
  4. La URL no contiene un elemento XML
Por ello debemos revisar siempre el parámetro, pues es lo único que puede hacer fallar esta función.

Bien, ahora ya tenemos en GlobalDoc nuestro documento cargado y listo para ser diseccionado. ¿Ahora qué?

Para explicar esta parte haré uso de un ejemplo sencillo:

Tendremos el siguiente fichero (imaginario) XML:

<Concesionario>
<Legal>
<Propietario num="1">DoHITB</Propietario>
</Legal>
<Vehiculos>
<Vehiculo matricula="1234ABC">
<Tipo>Coche</Tipo>
<Modelo>Seat</Modelo>
</Vehiculo>
<Vehiculo matricula="4321CBA">
<Tipo>Moto</Tipo>
<Modelo>Kawasaki</Modelo>
</Vehiculo>
</Vehiculos>
<Clientes>
<Cliente id="1">
<Nombre>DoHITB</Nombre>
</Cliente>
<Cliente>
<Nombre>DoH</Nombre>
</Cliente>
</Clientes>
</Concesionario>

Vemos que de "Concesionario" cuelga todo, es decir, es el nodo principal. Dentro de éste tenemos tres grandes secciones (podrían ser tres tablas en una base de datos):
  • Legal
  • Vehículos
  • Clientes
Cada uno de estos grandes nodos puede ser tratado de manera independiente, es decir, puede que yo solo quiera la parte de "Legal" para mostrarla en el pie de mi web; o puede que solo quiera los clientes para saber si el visitante de mi web está registrado.

¿Se ve bien que son casos independientes?


Algoritmo General:


Entonces, la idea fundamental es la siguiente:

"Yo te paso el nombre de la "sección" (una o varias), y tú las tratas".

El siguiente paso es, para cada sección (para hacer más versátil la herramienta, imaginemos que pudieran haber dos (o más) "secciones" con el mismo nombre) sería tratarla. Es decir:

"Yo te paso la/s "sección/es", y tú las tratas".

Y, finalmente, faltaría mirar el caso específico de cada elemento, y tratarlo.

"Para cada caso específico, tendrás que hacer una función".

Y aquí tenemos plasmado el algoritmo de tratamiento de XML.

Ahora os introduzco el código de estas tres funciones (líneas en cursiva):


Aplicar el algoritmo:


fill = function(currnode, doc){
for(i=0;i<currnode.length;i++){
treat(doc.getElementsByTagName(currnode[i]), currnode[i]);
}
};

Esta función admite dos parámetros:
  • currnode: lista de "secciones" a tratar
  • doc: documento XML (GlobalDoc)
Como veis, se itera sobre la lista, y por cada elemento de la lista, llamamos a la segunda función (treat) cogiendo todos los elementos cuyo "Tag Name" (el nombre de la etiqueta) sea el correspondiente al ítem de la lista, y también le pasamos el nombre de la "sección".

Segunda función, "treat", sería:

treat = function(nl, kindnode){
for(i=0;i<nl.length;i++){
_add(nl.item(i), kindnode);
}
};

Como decíamos, esta función recoge la lista de los ítem devuelta por "getElementsByTagName", y el nombre de la "sección".

Lo más normal, en este caso (sobretodo para el caso de este ejemplo) es que solo hubiera un nodo (una "sección") por cada llamada, pero como antes decía, lo dejamos así por si hubieran dos secciones "Legal" (por ejemplo).

Así, por cada iteración a "nl" (Node List - lista de nodos), llamaremos a la tercera función, "_add", siendo ésta:

_add = function(n, kind){
if(kind == "texto1"){
funcion1();
}else if(kind == "texto2"){
funcion2();
}

//...
};

Es decir, en este caso, iremos comparando "kind" con los valores que pudiera recibir (de momento "Legal", "Vehiculos", o "Clientes"). Es decir, en nuestro caso particular, y de momento:

_add = function(n, kind){
if(kind == "Legal"){
trataLegal();
}else if(kind == "Vehiculos"){
trataVehiculos();
}

//...
};

Y bien, falta una última cuestión: ¿cómo se "engancha" el resto de ítem a partir del que llega a "_add"?


La función puente:

Es decir, si entra en "if(kind == "Vehiculos")", yo quiero que me trate los vehículos, ¿no?

Para ello tenemos "trataVehiculos()", que queremos que haga lo siguiente:

"Para cada hijo de Vehiculos, tratalo".

Por lo que "trataVehiculos" deberá recoger los hijos de "n", tal que:

trataVehiculos = function(n){
for(i=0;i<n.length;i++){
_add(n, "Vehiculos:Vehiculo");
}
};

Es decir, para cada hijo de "n" en _add (cuando "kind == Vehiculos"), llamaremos a _add con la cadena "Vehiculos:Vehiculo". Por lo tanto, la función _add pasará a ser:

_add = function(n, kind){
if(kind == "Legal"){
trataLegal();
}else if(kind == "Vehiculos"){
trataVehiculos(n.childNodes);
}else if(kind == "Vehiculos:Vehiculo"){
add(new Array("Tipo", "Modelo"), n.childNodes, "Vehiculo", false);
}

//...
};

Como veis, he introducido la última función "add" - no confundir con "_add". Ésta hace lo siguiente:

add = function(fields, e, space, deep){
for(i=0;i<fields.length;i++){
for(j=0;j<e.length;j++){
if(e.item(j).nodeName == fields[i]){
if(deep){
_add(e.item(j).firstChild, space + ":" + fields[i]);
}else{
_add(e.item(j), space + ":" + fields[i]);
}

break;
}
}
}
};

Esta función (como el resto menos la primera, todas hechas por mi) recibe cuatro parámetros:
  • fields: Items a tratar (en el ejemplo: Tipo, Modelo)
  • e: Nodos a revisar (en el ejemplo, los dos nodos "Vehiculo")
  • space: primer fragmento de texto para llamar a "kind" (en el ejemplo: "Vehiculo")
  • deep: si está activado (true) usaremos el primer hijo del nodo (si estuviera activado, al buscar Vehiculo>Tipo, retornaría un valor nulo (ya que "Tipo" no tiene hijos); sin embargo, si recibiera "Cliente", retornaría el primer hijo: "Nombre"
Por lo que en la función _add, si "kind == "Vehiculos:Vehiculo"", se generarán dos llamadas:

_add(kind == Vehiculo:Tipo)
_add(kind == Vehiculo:Modelo)

De las cuales no tenemos más que extraer los datos que queramos, y tendremos todo lo que necesitamos.

Pero esto ya es tema de la siguiente entrada, en la que veremos cómo guardar estos datos, y los problemas que tuve con las clases.

Espero que sea útil todo esto...

¡Hasta la próxima!