22.10.13

Un XML no es un texto, es un binario

O tal vez debería matizar el título y decir que un XML no es un fichero de texto plano, y que habría que tratarlo a la hora de guardar y leer de disco, red, base de datos, o de donde sea, como un binario. Claro que entonces quedaría un título muy largo.

El motivo por el que hago esta afirmación tan tajante, tiene que ver con lo que expliqué hace dos posts: el famoso encoding. Como recordaréis, comentaba que la representación en bytes de un texto, depende del encoding utilizado. Leer un fichero de texto con la codificación correcta es vital para que no aparezcan símbolos inusuales en lugar de vocales acentuadas o nuestra querida eñe. Y esa codificación, si no la indicamos explícitamente en nuestra aplicación, el JRE usará la que tenga el sistema sobre el que corre. Así, un fichero de texto guardado en Windows, podría leerse de forma incorrecta en Linux, y viceversa, si usamos el encoding por defecto en ambos sistemas.

Con un XML no tenemos ese problema. El propio formato incluye una forma de especificar la codificación en el prolog (la cabecera) mediante el atributo encoding. Por ejemplo:


<?xml version="1.0" encoding="utf-8"?>
<ejemplo>
  ...
  Y aquí irá todo lo demás
  ...
</ejemplo>

Tanto el atributo encoding como el propio prolog son opcionales. Pero no hay problema, ya que el estándar establece mecanismos alternativos para determinar la codificación del XML, como la presencia de un BOM al principio. Y si no existe ninguna forma de determinar la codificación, se asume UTF-8 por defecto. Entre los mecanismos alternativos para determinar la codificación, no se tiene en cuenta la codificación de la plataforma. Es decir, un fichero XML puede viajar por varios sistemas diferentes, y todos deben usar la misma codificación para entenderlo, independientemente de la codificación que use el sistema para otros menesteres. Cualquier aplicación que lea el XML de ejemplo que he puesto antes, debería usar UTF-8 sí o sí.

Esto es fantástico, ¿no? ¿Dónde está entonces el problema? Pues ocurre que, dado que un XML es en el fondo texto entendible por un ser humano, hay desarrolladores que cometen el error de considerarlo como cualquier otro fichero de texto, y usan subclases de Reader y Writer, o incluso String para operar con él. Y eso es una bomba de relojería. Las clases anteriores interpretarán los bytes subyacentes con un encoding (el de la plataforma, o uno que se haya especificado de forma explícita) que no tiene por qué coincidir con el del XML. Si la codificación usada es la misma, pues no pasa nada. Pero un día, ya no coinciden (se migra la aplicación a otro entorno, se usan XMLs con otras codificaciones), y empiezan a aparecer caracteres raros, o peor aún, el parser es muy estricto y lanza una excepción si encuentra secuencias de bytes no válidas en UTF-8. Y entonces, alguien dice la gran frase «pero si esto siempre ha funcionado ¿por qué no funciona ahora?».

Para evitar este problema, basta con tener siempre en mente lo siguiente: hay que tratar un XML como si fuera un binario, y nunca como texto. Así, a la hora de leer un fichero, independientemente de la librería y parser utilizados, hay que usar aquellos métodos que pidan un InputStream, y nunca los que pidan un Reader. Para escribir un XML creado por nosotros, hay que huir de los métodos que usen un Writer como de la peste, y abrazar los que usen un OutputStream. La misma consideración hay que tener si guardamos XMLs en una base de datos relacional, por ejemplo. Nada de VARCHAR, CLOB o similares; debemos usar un BLOB. Y si llegado el caso tuviéramos que tener un XML en crudo en memoria, y usarlo como argumento o retorno de un método, debemos declararlo siempre como byte[], y nunca como String.

Cuando se usan XMLs muy pequeños (con pocos elementos y textos muy limitados), uno tiene la tentación de generarlos e interpretarlos «a pelo», sin tener que pasar por librerías sofisticadas. Por ejemplo, si tenemos que generar un XML como


<mensaje>Esto es un mensaje corto</mensaje>

parece un poco excesivo usar JAXB. Es mucho más simple generarlo a base de concatenar cadenas. Pero en este caso, el resultado final debe ser siempre un array de bytes, controlando nosotros (y no la plataforma) el encoding usado. Por ejemplo:


    public byte[] generarXml(String texto) throws UnsupportedEncodingException {
        return  ("<mensaje>" + texto + "</mensaje>").getBytes("UTF-8");
    }

puesto que como ya he comentado, si no se incluye prolog, la codificación por defecto es UTF-8 (y sí, el código es mejorable, pero se trata sólo de un sencillo ejemplo).

El caso contrario, interpretar un XML, no importa lo simple que pueda ser, creo que siempre es preferible el uso de un parser en condiciones. Pensad que sólo para averiguar el encoding, hay que hacer una primera lectura para buscar el prolog y su atributo encoding (si existen), y luego volver a leer otra vez con la codificación adecuada. Parece un trabajo que sólo se justificaría si tenemos unas limitaciones determinadas de memoria o tamaño de la aplicación (o algún otro dato del problema).