Alters

Aventuras de DoHITB: DoHITB vs. JavaScript (III)

Buenas!

Tercera parte de la aventura, llegamos al ecuador de esta misma...

Veremos ahora algunos problemas que fueron surgiendo y cómo se solucionan :-)


Nos quedamos en la anterior entrega en que teníamos unas funciones generales para tratar el XML desde JavaScript.

Tratar el documento: Funciones inferiores


Bien, vamos ahora a ver cómo guardar los datos en clases, de una manera "similar" a como lo haríamos con Java.

Partiendo de la base del documento XML anterior (si no lo recuerdas, aquí está), vamos a ver cómo agregaríamos un Vehiculo:

/*Supongamos que tenemos una clase llamada "Vehiculo"*/
addVehiculo = function(n){
vehiculo = new Vehiculo(n.attributes.getNamedItem("Matricula").nodeValue);
add(new Array("Tipo", "Modelo"), n.childNodes, "Vehiculo", false);
}

Esto es lo que se llamaría una "función inferior". Las he bautizado así porque son funciones que para cada caso cambiarán, mientras que las otras (de la entrada anterior, funciones superiores) siempre estarán presentes.

Expliquemos que hace esta función:

Genera un nuevo vehículo (Vehiculo) vacío, al que le pasamos el valor de la matricula
Después hacemos uso de "add" para pedir que inserte en Vehiculo los atributos "Tipo" y "Modelo".
El resto es cosa de "add", que delegará en "_add", que tendrá algo así como:

if(kind == "Vehiculo:Tipo"){
vehiculo.tipo = n.nodeValue;
}else if(kind == "Vehiculo:Modelo){
vehiculo.modelo = n.nodeValue;
}

Esto dejará los valores de "vehiculo" con sus respectivos valores.

Bien, visto esto, falta dar el siguiente paso en la historia:

Incorporar en una clase

Para aquellos que no lo lleven demasiado bien, decir que una clase es (a grosso modo) un fragmento de código que engloba variables y funciones, y permite su portabilidad y generalización, con la finalidad de facilitar la programación.

¿Qué?

Rápido, un ejemplo...

var Vehiculos = function(){
this.vehiculos = new Array();
this.vehiculosI = 0;

this.nuevoVehiculo = function(pVehiculo){
this.vehiculos[this.vehiculosI] = pVehiculo;
this.vehiculosI++;
}

this.getVehiculos = function(index){
if(index < 0){
return this.vehiculos;
}

return this.vehiculos[index];
}

this.findVehiculo = function(matricula){
fvr = -1;

for(fvi=0;fvi<this.vehiculosI;fvi++){
if(this.vehiculos[fvi].getMatricula() == matricula){
fvr = fvi;
break;
}
}

return this.getVehiculos(fvr);
}
}

Una vez definida la clase (si os fijáis es una variable), yo podría usarla así:

/*
Supongamos que tenemos otro objeto "Vehiculo" definido, al cual le podemos pasar una matrícula
*/

var misVehiculos = new Vehiculos();
var misMatriculas = ['1234ABC', '4321CBA', '6789XYZ', '9876ZYX']

for(i=0;i<misMatriculas.length;i++){
misVehiculos.nuevoVehiculo(new Vehiculo(misMatriculas[i]));
}

misVehiculos.getVehiculos(-1);//me retorna todos
misVehiculos.getVehiculos(2);//me retorna '6789XYZ';
misVehiculos.findVehiculos('4321CBA');

Y, como veis, incorporar la clase me aporta ciertas ventajas... pues bien, me decidí a meter el "parser" en una clase, y queda algo como esto:

var Parser = new Function(){
this.fill = function(currnode, doc){
//código...
}

this.treat = function(nl, kindnode){
//codigo...
}

this._add = function(n, kind){
//bloque de if's...
}

/*Bloque de funciones inferiores*/

this.add = function(fields,e,space,deep){
//código...
}
}

Se puede ver que ahora la clase es "portable"; es decir, yo podría aislar la clase en un script e incluirlo en cualquier página y trabajar con él. Parece bonito, ¿verdad?

Pero resulta que tuve un pequeño problema:

Problemas con la pila

Como dice arriba, tuve un serio y severo problema con la pila de JavaScript, que parece ir a su bola...

¿Pila? ¿De las de botón?

En realidad, cuando hablo de la pila me refiero a lo que en inglés se conoce por stack. Es una estructura en la que apilan cosas. En todo programa, script, etc debe haber una pila. Vamos a verlo:

Imaginad que yo tengo esto:

function first(){
a = 1;
alert(a);//mostrará "1"
second();
alert(a);//mostrará "1"
}

function second(){
a = 2;
alert(a);//mostrará "2"
}

function main(){
a = 0;
alert(a);//mostrará "0"
first();
alert(a);//mostrará "0"
}

¿Porqué funciona así? Sencillo: la pila.

Voy a proceder a explicar cómo funciona... si es ud. un entendido en el tema puede saltarse esta parte... ¡Vamos allá!

Como decía, en la pila se apilan cosas (¡Gracias!, capitan Obvio) de manera que el último elemento en entrar será el primero en salir (LIFO - Last In First Out / Último Dentro Primero Fuera). Ahora (no en plan sacrílego) recordemos como La Biblia define la primera pila LIFO ("los últimos serán los primeros").

Bueno, sigamos...

En esta pila podemos pensar (siempre a grosso modo) que se apila el estado actual de la ejecución de un programa. Y por cada llamada a una función, se genera un apilamiento.

Es decir, en el ejemplo anterior, podríamos representar algo como esto:

function first(){
//ya que está apilado el valor de "a", se puede definir otro "a", con valor 1
a = 1;
alert(a);//mostrará "1"
//antes de llamar a "second()", se apila el valor de "a", por lo que dentro de "second" no existirá
second();
//después de llamar a "second()", se recupera el valor de "a" en "first", que sigue siendo 1
alert(a);//mostrará "1"
}

function second(){
a = 2;
alert(a);//mostrará "2"
}

function main(){
//pila vacía
a = 0;
//se crea "a" en main, a = 0;
alert(a);//mostrará "0"
//antes de llamar a "first()", se apila el valor de "a", por lo que dentro de "first" no existirá
first();
//después de llamar a "first()", se recupera el valor de "a" en "main", que sigue siendo 0
alert(a);//mostrará "0"
}


Pero parece ser que JavaScript no cree en la Biblia, y por tanto no tiene pila LIFO... así que los valores de las clases se ven sobreescritos, por lo que somos nosotros los que debemos proporcionar la pila

¿En serio? En parte. Es decir, en el ejemplo anterior, en JavaScript, pasaría esto:

function first(){
//se sobrescribe el valor de "a"
a = 1;
alert(a);//mostrará "1"
//antes de llamar a "second()", se debería apilar el valor de "a", por lo que dentro de "second" no existiría
second();
//después de llamar a "second()", se debería recuperar el valor de "a" en "first", que debería seguir siendo 1
alert(a);//mostrará "2"
}

function second(){
        //se sobrescrive el valor de "a"
a = 2;
alert(a);//mostrará "2"
}

function main(){
//pila vacía
a = 0;
//se crea "a", a = 0;
alert(a);//mostrará "0"
//antes de llamar a "first()", se debería apilar el valor de "a", por lo que dentro de "first" no existiría
first();
//después de llamar a "first()", se debería recuperar el valor de "a" en "main", que debería seguir siendo 0
alert(a);//mostrará "2"
}

Solución

Como digo, la solución pasa por construir una pequeña pila. Realmente este problema se puede paliar usando variables con distintos nombre, pero en funciones recursivas como "this.add", los valores se sobrescriben, por lo que tenemos que acometer con el siguiente "parche":

this.add=function(fields,e,space,deep){
/*los valores siguientes "apuntan" a la parte superior de nuestra pila*/
/*al principio de cada llamada generamos un "nuevo puesto"*/
this.stackII++;
this.stackJI++;

/*estos array harán las veces de pila*/
/*al principio de cada llamada les damos valor 0 a los "nuevos puestos"*/
this.stackI[this.stackII] = 0;
this.stackJ[this.stackJI] = 0;

/*sustituímos "i" por stackI[stackII], y "j" por stackJ[stackJI], para usar las pilas*/
for(this.stackI[this.stackII]=0;this.stackI[this.stackII]<fields.length;this.stackI[this.stackII]++){
for(this.stackJ[this.stackJI]=0;this.stackJ[this.stackJI]<e.length;this.stackJ[this.stackJI]++){
if(e.item(this.stackJ[this.stackJI]).nodeName==fields[this.stackI5[this.stackII]]){
if(deep){
this._add(e.item(this.stackJ[this.stackJI]).firstChild, space+":"+fields[this.stackI[this.stackII]]);
}else{
this._add(e.item(this.stackJ[this.stackJI]), space+":"+fields[this.stackI[this.stackII]]);
}

break;
}
}
}

/*reiniciamos los valores del "puesto", solo por si acaso*/
this.stackI[this.stackII] = 0;
this.stackJ[this.stackJI] = 0;

/*nos vamos al "puesto anterior"*/
this.stackII--;
this.stackJI--;
};<

Y así, queda apañada la primera parte. Falta la parte de ordenación...

que veremos en la próxima entrada.

¡Hasta la próxima!