0. Índice de contenidos.
- 1.
Introducción. - 2.
Codificación de caracteres. - 3.
¿Caracteres «raros»?. - 4.
Especificar la codificación de caracteres. - 5.
Solución al problema de codificación en lenguajes de marcado. - 6.
Solución al problema de codificación en java. - 7.
Solución al problema de codificación en el entorno de un proyecto
gestionado por maven. - 8.
Un problema de difícil detección. - 9.
Conclusiones.
1. Introducción.
En este tutorial vamos a tratar de dar algo de luz a los errores que
tenemos habitualmente con la codificación de caracteres en aplicaciones
en las que se ven implicados varios sistemas que intercambian o
almacenan información. Vamos a ir de abajo a arriba, haciendo una
pequeña introducción a la codificación de caracteres, mostrando cómo
provocar esos errores, para terminar explicando cómo solucionarlos.
2. Codificación de caracteres.
La
codificación de caracteres
es el método que permite convertir un caracter del lenguaje natural, el
de los humanos, en un símbolo de otro sistema de representación,
aplicando una serie de normas o reglas de codificación. El ejemplo más
gráfico suele ser el del
código
morse
, cuyas reglas permiten convertir letras y números en señales (rayas y
puntos) emitidas de forma intermitente. En informática, las normas de
codificación permiten que dos sistemas intercambien información usando
el mismo código numérico para cada caracter. Las normas más conocidas de
codificación son las siguientes:
- ASCII:
basado en el alfabeto latino tal como se usa en inglés moderno y en
otras lenguas occidentales. Utiliza 7 bits para representar los
caracteres, aunque inicialmente empleaba un bit adicional (bit de
paridad) que se usaba para detectar errores en la transmisión.
Incluye, básicamente, letras mayúsculas y minúsculas del inglés,
dígitos, signos de puntuación y caracteres de control, dejando fuera
los caracteres específicos de los idiomas distintos del inglés, como
por ejemplo, las vocales acentuadas o la letra ñ. - ISO-8859-1 (Latin-1): es una extensión del código
ascii que utiliza 8 bits para proporcionar caracteres adicionales
usados en idiomas distintos al inglés, como el español. Existen 15
variantes y cada una cubre las necesidades de un alfabeto diferente:
latino, europa del este, hebreo cirílico,… la norma ISO-8859-15, es
el Latin-1, con el caracter del euro. - cp1252 (codepage 1252): Windows usa sus propias
variantes de los estándares ISO. La cp1252 es compatible con
ISO-8859-1, menos en los 32 primeros caracteres de control, que han
usado para incluir, por ejemplo, el caracter del euro. - UTF-8:
es el formato de transformación Unicode, de 8 bits de longitud
variable. Unicode es un estándar industrial cuyo objetivo es
proporcionar el medio por el cual un texto en cualquier forma e idioma
pueda ser codificado para el uso informático. Cubre la mayor parte de
las escrituras usadas actualmente.
En la enumeración hemos ido de menos a más, no solo en el tiempo, por el
momento de aparición de la norma, sino también por los caracteres que
soporta cada una, UTF-8 es la más ambiciosa. Visto así, la recomendación
debería ser el uso de UTF-8 puesto que, escriba en la lengua que
escriba, sus caracteres van a ser codificables. Pero, si sólo escribo en
castellano, podría limitarme a usar ISO-8859-1, o ISO-8859-15 si
necesito el caracter del euro, sin ningún problema. Más relevante que la
norma de codificación que se use para escribir, que no es poco, es ser
conscientes que la lectura se debe realizar con la misma norma de
codificación con la que se escribió. Y tiene toda la lógica del mundo,
puesto que si escribimos un fichero con ISO-8859-1 no debemos esperar
que un sistema que lee en UTF-8 lo entienda sin más (aunque realmente
entienda gran parte).
3. ¿Caracteres «raros»?.
Los caracteres «raros» aparecen por una conversión incorrecta entre dos
codificaciones distintas. Se suelen producir porque se utiliza la
codificación por defecto del sistema o programa y esta no coincide con
la original o, directamente, por desconocimiento de la norma de
codificación de la fuente de lectura. Así, por ejemplo, podemos
encontrarnos con los siguientes caracteres «raros» escribiendo la misma
palabra:
- España → España: si escribimos en UTF-8 y
leemos en ISO-8859-1. La letra eñe se codifica en UTF-8 con dos bytes
que en ISO-8859-1 representan la A mayúscula con tilde (Ã) y el
símbolo más-menos (±). - España → Espa�a: si escribimos en ISO-8859-1
y leemos en UTF-8. La codificación de la eñe en ISO-8859-1 es inválida
en UTF-8 y se sustituye por un caracter de sustitución, que puede ser
una interrogación, un espacio en blanco… depende de la
implementación.
Podemos provocar un error fácilmente haciendo uso de un editor que
permita modificar el formato de escritura, como
pspad
, y utilizando un lector que permita modificar el de lectura, como por
ejemplo un navegador .
Si nos encontramos frente a una aplicación web cliente-servidor tenemos,
como mínimo, los siguientes actores implicados: una base de datos con un
set de caracteres, una aplicación escrita en un lenguaje que usará su
propio encoding para las lecturas y escrituras en esa base de datos y en
el sistema de ficheros, un servidor web dinámico o un servidor de
aplicaciones que servirá peticiones a un cliente escribiendo en la
respuesta con una codificación preestablecida y un cliente que debe leer
la respuesta del servidor. Todos esos
actores del proceso deben usar la misma norma para
leer y escribir
, a ser posible estándar, lo deseable: UTF-8.
4. Especificar la codificación de caracteres.
Para evitar problemas con la codificación, siempre debemos indicar
explícitamente en nuestros fuentes y sistemas de lectura con qué norma
estamos trabajando, con ello le indicaremos al lector la regla de
codificación. En HTML con la siguiente etiqueta en la cabecera del
documento
En XML con el valor del atributo encoding (por defecto es UTF-8):
Con ello reducimos los posibles problemas pero no los evitamos, puesto
que no sirve de nada indicar en el fuente de un fichero html que su
encoding es utf-8 si, al guardarlo en disco, lo guardo con un encoding
distinto o el servidor lo lee con un encoding diferente para servirlo.
Y, ¿que ocurre con aquellos fuentes en los que no se indica el
encoding?, como fuentes java o ficheros de propiedades. Los primeros son
leídos por el compilador y los segundos por el propio fuente java
compilado, en runtime. La Máquina Virtual Java (JVM) utiliza una
codificación por defecto, que suele ser la del sistema operativo donde
se está ejecutando, en windows cp1252. La codificación por defecto se
puede conocer obteniendo la propiedad del sistema «file.encoding» y se
puede modificar programáticamente (asignando valor a dicha propiedad) o
en la compilación. Si bien, volvemos a tener el mismo problema, puesto
que, de nada sirve que se asigne dicha propiedad si, al escribir el
fuente java, estoy guardando con una codificación distinta. Con lo
dicho, la preocupación no es sólo el encoding asignado al documento en
su fuente, sino el encoding con el se escribe el mismo.
5. Solución al problema de codificación en lenguajes de
marcado.
La solución al problema de codificación en lenguajes de marcado como
html o xml pasa por hacer uso de entidades que sustituyen los caracteres
no incluidos en la codificación básica (ascii). Dichas entidades son una
clave escrita con los propios caracteres de la norma ascii, aunque
también podemos hacer uso de la correspondiente clave del caracter en
unicode o de su código en decimal. Con ello, son interpretados
independientemente de la codificación, puesto que no usan ningún
caracter fuera del código ascii. Así, podemos acudir a cualquier
tabla de referencia
en las que se incluyen los caracteres y su correspondiente entidad,
clave en unicode y en decimal. Por incluir algún ejemplo:
Caracter | Entidad | Unicode | Decimal |
---|---|---|---|
« | quot | U+0022 | 34 |
á | aacute | U+00E1 | 225 |
ñ | ntilde | U+00F1 | 241 |
En cualquier fuente xml o html, independientemente del encoding asignado
a la cabecera y del usado para guardar el documento, podemos incluir
cualquiera de esas tres claves para sustituir su correspondiente caracter
de la siguiente forma:
Character encoding Usando el nombre de la entidad: España
Usando el código unicode: España
Usando el código decimal: España
Podéis probar a guardar el documento en UTF-8 y visualizarlo desde un
navegador en ISO-8859-1, los únicos caracteres que no se mostrarán
correctamente son las oes acentuadas, puesto que no están escapadas.
Algo tedioso si hubiese que hacerlo manualmente… para ello las
herramientas de edición de páginas web
realizan esa conversión a entidades automáticamente. E insisto, esta
conversión es del todo innecesaria si especificamos el encoding UTF-8 en
la cabecera de nuestros fuentes, guardamos el fichero con ese mismo
encoding, y todos los actores implicados en el proceso de lectura usan
esa misma norma de codificación.
6. Solución al problema de codificación en java.
Volvemos a los fuentes java y ficheros de propiedades que nos permiten
internacionalizar nuestra aplicación; ahora se almacenan en un
repositorio de código tipo subversion y son compartidos por un equipo de
desarrollo más o menos dimensionado. ¿En qué codificación se almacenarán
esos ficheros en el repositorio de código?, trataremos que sea en UTF-8,
pero más importante será que todo el equipo de desarrollo tenga asignado
esa misma codificación en sus entornos de desarrollo o IDEs. Un problema
común se da trabajando con Eclipse en una plataforma windows, puesto que
viene configurado por defecto con el encoding cp-1252, porque es el de
por defecto de la JVM en ese SO. Si los fuentes están en el repositorio
en UTF-8 y me los descargo sin preasignar dicha codificación, he perdido
todas las tildes de los comentarios del fuente java (y quizás el valor
de alguna constante de tipo cadena) y, peor aún, de los literales
internacionalizados de los ficheros de propiedades. No es mayor
problema, si me doy cuenta y modifico el encoding a UTF-8, pero si no
caigo en ello y hago commit sin modificarlo, he cambiado la codificación
del fuente en el repositorio de código y lo he subido con errores de
codificación, con caracteres «raros». Para solucionar este problema
podemos hacer uso de las claves unicode en el fuente de nuestros .java o
.properties. Así, por ejemplo:
path = path.replace('\u00E1','a');
donde
00E1
se corresponde con la a acentuada, y el código reemplazaría de la cadena
las aes acentuadas por aes sin acento. Al igual que el punto anterior,
algo tedioso para hacerlo manualmente… para ello la propia JDK
distribuye una utilidad llamada
native2ascii
que convierte un fichero de una codificación nativa (no Unicode o no
Latin-1) a otro en ascii con caracteres codificados en unicode. Previene
el problema de la codificación de caracteres convirtiendo el contenido
de los ficheros en un formato que puede ser almacenado como ascii. Y se
suele usar para la internacionalización de los ficheros de mensajes
(ficheros de propiedades) de manera que puedan ser leídos por las clases
Properties y ResourceBundle, sin necesidad de una configuración
específica del entorno. Es una utilidad por línea de comandos que puedes
encontrar en el mismo directorio que el compilador, y que permite:
- introducir caracteres para que los convierta y les de salida
por consola - introducir un path a fichero y que lo convierta dándole salida
por consola o a fichero.
Volvemos a recordar la importancia de que exista una sincronía entre la
codificación de lectura y escritura, puesto que native2ascii es una
utilidad java que usará para leer el encoding por defecto de la JVM, si
no lo modificamos, será el del SO. Como consecuencia, si nuestro fichero
de propiedades está escrito en UTF-8, y no le indicamos lo contrario, la
utilidad le leerá como cp1252 en windows, y sustituirá todos los
caracteres que no estén incluidos en dicha codificación como \ufffd. De
ahí la necesidad de indicar el parámetro
-encoding
.
7. Solución al problema de codificación en el entorno de un
proyecto gestionado por maven.
Maven gestiona todo el ciclo de vida de nuestro proyecto: compilación,
tests, empaquetamiento, versionado,… en base a plugins que
configuramos en nuestros pom.xml. Esos plugins están escritos en java y
leen y escriben nuestros ficheros, para lo cuál necesitan la
especificación de un encoding. Por defecto, usan el de la JVM, esto es,
el del SO, con lo que si estamos en windows será cp-1252. Todos esos
plugins aceptan la configuración del encoding de lectura y escritura, y
es necesario configurarlo.
maven-compiler-plugin 1.5 UTF-8 org.apache.maven.plugins maven-resources-plugin UTF-8 org.apache.maven.plugins maven-site-plugin UTF-8 UTF-8
Podéis comprobar que lo que hemos hecho es escribir UTF-8 en la
configuración de todos los plugins que aceptan la asignación de
encoding. Si bien, para no repetir la configuración podríamos haber
declarado una propiedad y usarla como una variable o, mejor aún, usar
directamente una propiedad preestablecida que asigna un
encoding por defecto para todos los plugins
de nuestros pom.xml.
UTF-8 UTF-8
Con ello ahorramos configuración, puesto que ya no es necesaria la
configuración individual de cada uno de ellos. Y para nuestros ficheros
de propiedades existe un
plugin que permite invocar de manera automática, en
la fase de empaquetación, a la utilidad native2ascii
, permitiendo realizar la conversión de caracteres de ciertos ficheros
dentro del propio ciclo de vida del proyecto.
org.codehaus.mojo native2ascii-maven-plugin 1.0-alpha-1 src/main/resources target/classes native2ascii UTF8 messages*.properties
Se incluye un directorio de origen y destino de los ficheros, el
encoding de los fuentes y un patrón para detectar qué ficheros deben ser
convertidos, en el ejemplo, todos los ficheros de propiedades que
comiencen por «messages». Aún todo lo anterior, los problemas no estarán
del todo solucionados porque siempre existe la posibilidad de que
alguien suba al repositorio de código un fichero de propiedades en un
formato distinto a UTF-8, con lo que el encoding de escritura difiere del
de lectura y la conversión de native2ascii será errónea. En tal caso
solo queda identificar al culpable y proceder su escarnio público, con
la posibilidad de establecer una sanción económica por haber «roto» el
proyecto.
8. Un problema de difícil detección.
Sigo en el mismo proyecto, gestionado por Maven, y tengo que hacer una
modificación en un fuente. Se me ocurre que por ser lunes y las 11:15 no
voy a hacerlo desde el IDE, desde Eclipse, voy a acceder mediante el
explorador de windows para hacerlo desde el block de notas. El fuente
estaba en UTF-8 y el notepad al guardar en ese encoding introduce una
marca BOM (
byte
order mark
) en el comienzo del fichero. Esos caracteres pueden provocar que el
compilador java falle y, si el fuente es un xml, como puede ser un
pom.xml de maven, el proyecto dejará de compilar puesto que se trata de
un xml mal formado.

La solución pasa por encontrar un editor que permita primero leer esos
caracteres y eliminarlos del fuente después. Para ello,
jedit
tiene un plugin para editar en hexadecimal.
9. Conclusiones.
La codificación es uno de esos temas de los que sí debemos ser
conscientes y espero que este tutorial haya ayudado a ello o, al menos,
a prevenir ciertos errores comunes. Un saludo. Jose