Wednesday 1 June 2011

Eliminar caracteres raros o binarios de un string en PHP

Bueno, espero que estés aquí porque te hayas encontrado con el siguiente problema: tienes carácteres "extraños" o "binarios" o "no ascii" en algunos "strings" de tu base de datos, y no sabes cómo filtrarlos. Estos carácteres aparecen habitualmente al hacer un "copiar y pegar" desde algún software de Microsoft (Outlook, MSword, MSexcel, etc...) de usuarios que estén rellenando un formulario en tu web, por ejemplo. Me imagino que es el caso más habitual :(

Lo más curioso es que el hecho de que tengas un carácter de ese tipo infiltrado en tu base de datos puede ocasionar los errores más variopintos y puede costar de llegar a descubrir que es por culpa de ese maldito carácter! En mi caso estaba pasando datos de un servidor a otro usando un webservice que trabaja con SOAP y que por tanto los datos se envían en un formato XML perfecto. Eso quiere decir que si en la cadena de datos que estás transmitiendo tienes un caracter raro de esos... entonces ya rompes el formato del XML y recibes un error del tipo: "documento XML mal formado".

Solución

Aquí os dejo una función que a mí me sirve para limpiar de carácteres raros una cadena de texto (string). Uso PHP 5.2 corriendo sobre CentOS (linux), por lo que no sé si habrá diferencias con otros entornos, pero por probar no pierdes nada, y en todo caso la solución va por aquí:

1  <?php
2  
function f_remove_odd_characters($string){
3       
// these odd characters appears usually 
4       // when someone copy&paste from MSword to an HTML form
5       
$string str_replace("\n","[NEWLINE]",$string);
6       
$string=htmlentities($string);
7       
$string=preg_replace('/[^(\x20-\x7F)]*/','',$string);
8       
$string=html_entity_decode($string);     
9       
$string str_replace("[NEWLINE]","\n",$string);
10       return 
$string;
11  }
12  
?>


Unos pocos comentarios:
  • he mezclado lo mejorcito que he encontrado por ahí que no era mucho, pues al final he tenido que poner de mi parte ;)
  • lo del NEWLINE es para evitar que el preg_replace elimine los posibles cambios de línea
  • lo de los htmlentities es para evitar que se el preg_replace elimine las posibles vocales acentuadas y otros carácteres latinos!
  • efectivamente, el mérito de la limpieza se lo lleva la función preg_replace, habiendo protegido previamente las vocales acentuadas y los cambios de línea

Por favor, si no te ha servido y has podido "modificarla" para hacerla funcionar en tu caso, se agradecería que escribieras los detalles. ¿Te puedes creer que dónde he leído la mayor parte de esa función ha sido en un artículo que se escribió hace dos años y medio y aún la gente sigue aportando mejoras y comentarios?... qué alegría me da eso :)

Este era el enlace original, en dónde aporté mis cambios:
http://www.stemkoski.com/php-remove-non-ascii-characters-from-a-string

Actualización 27-Junio-2011


Acabo de descubrir que la función de arriba tiene problemas con el símbolo euro!! (€). Después de investigar, descubro que este símbolo no está en el charset latino habitual ISO-8859-1 y que por tanto la función nativa de PHP utf8_decode("€") devuelve "?" !!!! :(

Esto provoca que si el charset de tu web no es utf-8 entonces los simbolos de euro se te convierten en un interrogante si les aplicas utf8_decode() para guardarlo en tu base de datos! por si no lo sabías... Pero en fin, normalmente no deberías hacer esas cosas. Es decir, tener la base de datos y al web en dos charsets diferentes!

Bueno, en paralelo, si aplicamos la función f_remove_odd_characters($string) sobre el simbolo de euro, éste SIMPLEMENTE desaparece :( es decir, lo convierte a ''. De lo cuál me di cuenta cuando me fueron despareciendo los simbolos de euro que mis usuarios introducían en los formularios :(((

Conclusión, dejo pendiente este asunto de la eliminación de caracteres. He encontrado por internet una función que sustituye a utf8_decode() de manera que "evita" el problema del euro... por si a alguno le sirve, y también quiero ver si con ello también se eliminan otros caracteres raros como los que pretendía limpiar al inicio.

Es una lástima que no tenga ya "guardados" en mi base de datos esos carácteres raros para poder probar si efectivamente se pueden limpiar con la siguiente función, pero en fin, si alguien puede hacerlo, se lo agradeceré enormemente si comparte aquí el resultado ;)

1  <?php
2  
3  
function _utf8_decode($string){          
4       
5      
/* Only do the slow convert if there are 8-bit characters */
6      /* avoid using 0xA0 (\240) in ereg ranges. RH73 does not like that */
7      
if (! ereg("[\200-\237]"$string) and ! ereg("[\241-\377]"$string))
8          return 
$string;
9  
10      
// decode three byte unicode characters
11      
$string preg_replace("/([\340-\357])([\200-\277])([\200-\277])/e",       
12      
"'&#'.((ord('\\1')-224)*4096 + (ord('\\2')-128)*64 + (ord('\\3')-128)).';'",   
13      
$string);
14  
15      
// decode two byte unicode characters
16      
$string preg_replace("/([\300-\337])([\200-\277])/e",
17      
"'&#'.((ord('\\1')-192)*64+(ord('\\2')-128)).';'",
18      
$string);
19  
20      return 
$string;
21  } 
22  
23  
?>