domingo, 29 de noviembre de 2009

Nota rápida: Espacios de nombres en XML

Los espacios de nombres (namespaces) sirven para definir un conjunto de palabras clave en determinada situación (en este caso, dentro de XML). Así, un nodo en XML que se defina <table> sin un espacio de nombres es un nodo cualquiera, pero si usa el espacio de nombres de HTML, indica el inicio de una tabla, y de igual forma si creamos un espacio de nombres propio y lo definimos en un archivo aparte, podemos hacer que signifique lo que queramos.

Ahora mismo estoy diseñando un portal web del que no voy a hablar porque es un proyecto confidencial, y entender lo que voy a explicar me tomó al menos 2 semanas descubrirlo por mí mismo, porque en Internet no encontré suficiente información.

El planteamiento es este: tenemos una web basada en AJAX y queremos que al presionar un botón "Action!" (que es como llamo a mis botones cuando ando testeando algo y necesito un nombre rápido, corto y preciso) aparezca en determinado lugar (ya sea una celda de una tabla, un Div o en medio de un párrafo) cierto código con formato, pongamos de ejemplo que queremos crear un <strong>Holaa!</strong>.

Lo que se les vendrá a la cabeza a algunos es crearlo con la función createElement() del DOM y anexarlo, pero hay que recordar que es un ejemplo, ya que el bloque de código que yo necesito anexar es muy largo y dinámico como para crear una función de JavScript que maneje todos los cambios. De hecho lo creo con una función en PHP que precisamente quiero reutilizar para el caso.

Tenemos ya dos condiciones iniciales: usar la función en PHP que ya dispongo en lugar adaptar una en JavaScript, y que la creación del bloque se haga al instante, sin recargar la página. Hay una tercera condición: no usar innerHTML().

¿Por qué no usar innerHTML()? Por cuatro razones:


  • No es un estándar de la W3C, y yo programo siguiendo estándares porque me parece un excelente trabajo el que están haciendo los chicos del consorcio por la red, y definitivamente pienso respetarlo.

  • No funciona en algunos agentes de usuaio. Al ser propiedad de Microsoft, es posible que haya navegadores que no lo soporten y existan problemas al momento de crear aplicaciones AJAX, y francamente no están obligados a soportarlo.

  • Dentro de poco quedará obsoleto. A pesar de que hay algunos navegadores que lo soportan además del Internet Explorer, la estandarización de la web está orillando a esta función a desaparecer; en el futuro los lenguajes basados en XML (como el mismo XML y XHTML, por ejemplo) no lo reconocerán y no darán soporte a él, delegando su uso solo a los programadores quisquillosos que por alguna noble causa insisten en programar doble código para hacer sus sitios compatibles con todos los navegadores.

  • Otorga poca flexibilidad. innerHTML() es una función que agrega una cadena de texto al código. A diferencia de hace más de una década, el código HTML de una web no es solo un texto que se va leyendo e interpretando, sino que ahora los navegadores cuentan con una interfaz llamada DOM que jerarquiza los elementos de una página web y brinda una API para acceder a ellos (DOM es la API) y manipularlos, permitiendo la creación, modificación y borrado de elementos de una manera muy organizada y práctica. innerHTML inyecta un pedazo de texto para ser leído, pero al ser un String no se anexa al DOM y no podrá ser modificado por su API. Hasta ahora no sé si vaya a necesitar modificar el DOM del bloque de código que estoy anexando pero aún así volvemos al punto uno: me manejo con estándares.



Ya que no vamos a usar innerHTML(), solo nos queda una forma de hacerlo: la correcta: usando appendChild().

Queda así:

abrimos una conexión asincrónica con el servidor y nos preparamos para intercambiar los datos. Después de leer detenidamente sobre los métodos de acceso al servidor y evaluar las necesitades del módulo que estoy programando, decido que GET es la mejor opción:

var Connection = new XMLHttpRequest();
var DatosRecibidos;
const DONE = 4;

function CrearConexion(){
var Connection = new XMLHttpRequest();
Connection.open("GET", "Respuesta.php");
Connection.onreadystatechange = ManipularDatos;
Connection.send();
}

function ManipularDatos(){
if (Connection.readyState == DONE){
DatosRecibidos = Connection.responseXML;
var ItemCell = document.getElementById("MiCelda");

//Si tiene contenido, se lo borramos
if (ItemCell.firstChild) ItemCell.removeChild(ItemCell.firstChild);

//Aquí anexamos
ItemCell.appendChild(DatosRecibidos.childNodes[0]);
}


Lo que en teoría hace esta función es justo lo que necesitamos: conecta correctamente al servidor, accede a la página que genera el bloque de código que necesitamos incluir, lo descarga y lo incluye al código fuente dinámico, es decir que ir a "Ver / Ver código fuente de esta página" no nos conduce a nada, porque nos muestra el código que se generó al cargar la página, pero no contiene el código que se ha creado dinámicamente; para eso tenemos que seleccionar el pedazo de página y con el click derecho escoger "Ver código fuente de esta selección".

Allí podremos ver el bloque de código de la página con el código anexado, pero el pedazo que agregamos estará sin formato. Podemos ver etiquetas de negrita, cursiva, subrayado, texto de colores y al voltear a la página web veremos que en efecto, el texto está formateado. Pero cuando nos ponemos a observar el código que generamos dinámicamente, por más que tenga etiquetas strong, span, div, table, tr/td, lo que se visualizará en el navegador será texto plano sin formato.

Me estuve preguntando más de 2 semanas ¿por qué? y ¿cómo resolverlo? Leí por varios lados pero no encontré nada útil, pues resulta que todos andan usando innerHTML().

Despueś de leer un manual de XML con buena información y hacer docenas de experimentos con mi código en JavaScript, caí en que era cuestión del espacio de nombres de XML.

Para poder anexar código usando appendChild() en lugar de innerHTML() debemos obtener la información con responseXML() en lugar de responseText(), como lo pueden ver en mi código, pero para que responseXML nos devuelva un objeto con la información XML transformada en DOM, la información debe ser un documento XML (es decir, NO texto plano, no HTML, sino XML) que previamente lo debimos haber especificado en PHP con header("Content-Type: text/xml");.

Al recibir información XML, el User Agent la incluyó en el DOM como información XML y ocurrió lo que hablé iniciando el post: las etiquetas table, div, td, etc. que tenía mi código no contaban con un espacio de nombres definido, por lo que para el parser eran etiquetas que yo me había inventado. Se veían en mi código fuente dinámico como si fueran de HTML pero lo cierto es que venían en calidad de visitantes... extranjeros que habían ingresado con pasaporte y VISA.

Una vez que me di cuenta de ello, me tomé unos minutos para re checar en el manual de XML que había leído la correcta inclusión de namespaces. De modo que para que un código pueda ser bien recibido y formateado, debe pasar de un simple:

<strong>Holaa!</strong>


a un estructurado:

<?xml version="1.0" encoding="UTF-8" ?>
<strong xmlns="http://www.w3.org/1999/xhtml">Holaa</strong>


Allí está el espacio de nombres indicado.


Hice este post porque no vi que esto se aclare en ningún manual, espeificación, foro o blog hasta donde consulté, y es un dolor de cabeza no tener idea de qué, quién, por qué, por dónde o por cuándo. Si alguien conoce una manera más sencilla de hacer esta transacción y que siga los estándares, no dude en postear.