Dividir correctamente un string en caracteres. No con string.split(“”)


Dividir un string en caracteres parece una tarea sencilla, y lo es. mientras un carácter sea solo un carácter.

Como así?

En unicode existen caracteres que se combinan con otros, ejemplo de estos son los caracteres de acento. De esta forma a simple vista un carácter puede parecer un solo símbolo pero estar formado por dos o mas.

Por ejemplo la letra “a” con tilde puede ser un solo carácter (u+00e1), pero puede darse el caso que sea una letra a + una tilde (a+’) que son dos caracteres (u+0061 y u+0301 respectivamente).

Esto es un problema, en especial para una aplicación que necesite acceder a cada uno de los caracteres de una palabra como en los crucigramas, ahorcado, sopas de letras, etc.

Por ejemplo en mi juego de sopa de letras a veces las palabras en la cuadricula quedaban en la posición incorrecta. El problema, algunas palabras usaban mas espacio debido a los caracteres de combinación (eje. “mamá” ocupando 5 cuadros = m | a | m | a | ‘ |).

Que puede salir mal

Para conocer el largo de una cadena de texto normalmente usamos la propiedad length… error, ésta solo da resultados correctos si cada carácter en el texto esta formado por un solo codepoint.
alert("mamá".length)

Gracias a un enlace en MDN (Mozilla Developer Network) encontre esta solución.  para obtener el largo correcto del string contamos los caracteres que no son marcas de combinación. a continuación la función (con algunas modificaciones).
// regexp = rangos de caracteres correspondientes a marcas de combinación
rCombiningMarks = /[u0300-u036Fu1DC0-u1DFFu20D0-u20FFuFE20-uFE2Fu0483-u0489u0591-u05BD]/g;

function stringLength(string) {
// iniciar un contador de caracteres en 0
var length = 0;

// por cada caracter en string
for (var i = 0, l = string.length; i < l; i++) {
// si no es una marca de combinación
if (! rCombiningMarks.test(string.charAt(i)) )
// incrementar el contador de caracteres
length += 1
}

return length;
}
Probamos nuestra función.
alert(stringLength("mamá"));


Como dividir un string?

Podemos usar un método similar al anterior para dividir un string:
// regexp = rangos de caracteres correspondientes a marcas de combinación
rCombiningMarks = /[u0300-u036Fu1DC0-u1DFFu20D0-u20FFuFE20-uFE2Fu0483-u0489u0591-u05BD]/g;

function splitString(string) {
// iniciar un array de caracteres
var chars = [],
lastChar;

// por cada caracter en string
for (var i = 0, l = string.length; i < l; i++) {
// si no es una marca de cominación
if (! rCombiningMarks.test(string.charAt(i)) ) {
// Agregar al array de caracteres
chars.push(string.charAt(i));
}
// de lo contrario
else {
// Concatenar con ultimo caracter en el array
var lastChar = chars[chars.length - 1];
chars[chars.length - 1] = lastChar + string.charAt(i);
}
}

return chars;
}

// TEST
alert("con string.split: "+("mamá".split("").join(" | ")));
alert("con splitString: " + splitString("mama").join(" | "));


Conclusión

Aun cuando javascript nos provee varios métodos útiles para el manejo de strings, números, arrays y objetos, estos no siempre nos darán los resultados deseados. debemos probar nuestro código para estar seguros que se comporta de la forma correcta.

Algún otro método/función nativa de javascript de la que no hay que confiar. Déjalo en los comentarios.