Archive for the 'SimpleXmlDoc' Category

SimpleXmlDoc, parseando XML en JavaScript/NodeJS (1)

Monday, January 25th, 2016

De vez en cuando, en mis proyectos públicos y no públicos de NodeJS, me toca analizar (parsear) un texto XML. Por ejemplo en NodeDelicious. En esos casos suelo usar la librería xml2js. XML no es uno de mis formatos favoritos, pero siempre hay alguna API que aparece por ahí que no se enteró del cambio de siglo y sigue entregando XML en vez del tan ubicuo JSON.

Como ejercicio de TDD, escribí un simple parser de XML, cumpliendo con los casos que tengo previstos para un proyecto no público. Siempre trato, siguiendo baby steps, simplicidad, y casos de uso, implementar solamente lo que necesito. Pueden ver el resultado en:

https://github.com/ajlopez/SimpleXmlDoc

y evidencia de seguir el flujo de trabajo de TDD en el historial:

https://github.com/ajlopez/SimpleXmlDoc/commits/master

Comencé con algunos tests simples, como reconocer un elemento en un texto:

var elements = require('../lib/elements');

exports['get element'] = function (test) {
    var element = elements.element("");
    
    test.ok(element);
    test.equal(typeof element, 'object');
};

Vean que puse un archivo aparte, elements.js, dedicado a implementar el análisis y proceso de cada elemento de un documento. La API principal está expuesta en el archivo simplexmldoc.js, que recupera y usa a elements.js. Pero al comienzo, me concentré en los primeros tests de elementos. Otro ejemplo al que llegué en algún momento:

exports['get autoclose element'] = function (test) {
    var element = elements.element("");
    
    test.ok(element);
    test.equal(typeof element, 'object');
    test.equal(element.elements().count(), 0);
    test.equal(element.text(), '');
};

O reconocer un namespace:

exports['get element with namespace'] = function (test) {
    var element = elements.element("");
    
    test.ok(element);
    test.equal(typeof element, 'object');
    test.equal(element.ns(), 'ns');
    test.equal(element.tag(), 'tag');
    test.equal(element.name(), 'ns:tag');
};

¿Cómo fue siendo implementado un elemento? Bueno, siguiendo TDD y baby steps, se fue implementando de a poco. En vez de definir toda su programación de una vez, fue creciendo en capacidades a medida que se plantearon nuevos casos de uso en los tests.

Como quería procesar un texto, sin partirlo directamente en palabras y otros tokens, vean cómo fue quedando internamente la implementación de un elemento:

function Element(text, offset) {
    offset = offset || 0;
    offset = find.open(text, offset);
    
    var tag = parseName(text, offset + 1, 'tag');
    var ns = null;
    
    if (text[tag.offset + tag.length] === ':') {
        ns = tag;
        tag = parseName(text, tag.offset + tag.length + 1, 'tag');
    }
    
    var nextgt = find.close(text, tag.offset + tag.length);    
    var nextlt = find.open(text, nextgt + 1);
    var elements = [];
    
    // ..... 

Un elemento tiene un texto y un desplazamiento dentro de ese texto. Las funciones auxiliares find.open y find.close se encargan de ubicar el comienzo de un elemento con < (menor) y el fin de un elemento (o de su comienzo) con > (mayor).

Al usar texto y desplazamiento, el texto original (posiblemente el documento completo), no tiene que partirse en otros textos, strings. Sino que queda de una pieza, y solamente cada elemento tiene punteros/desplazamientos a donde comienza y donde termina cada elemento. Esa implementación queda totalmente oculta en la API expuesta. Y si se me ocurre una nueva forma de implementarla, dado los tests de la API, tengo el soporte necesario para cambiar la programación interna, sin romper algo (o por lo menos, los tests me avisarán sin rompí algo).

Como siempre remarco, esto es lo que da TDD junto con búsqueda de simplicidad y baby steps: el desarrollo del software que estamos construyendo, de a poco, como si fuera un organismo que va creciendo. Notablemente, mi experiencia indica que esta forma de trabajo permite ir acomodando nuevos casos de usos, sin complicar lo escrito. Es rato que tenga complejidad accidental en un proyecto escrito de esta forma.

Por supuesto que debe haber librerías más poderosas. Pero es interesante programarla, y entrenarse en TDD. Además, ya la estoy consumiendo en un proyecto no público, siempre hacer “dog fooding”.

Próximos posts: alguna descripción de la API expuestas, casos de uso, conversión de y a objetos JavaScript, y de y a strings

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez