javascript - simbolos - formato utf-8
Problemas de codificación para el archivo CSV UTF8 al abrir Excel y TextEdit (6)
Recientemente agregué un botón de descarga de CSV que toma los datos de la base de datos (Postgres) una matriz del servidor (Ruby on Rails), y los convierte en un archivo CSV en el lado del cliente (Javascript, HTML5). Actualmente estoy probando el archivo CSV y me encuentro con algunos problemas de codificación.
Cuando veo el archivo CSV con ''menos'', el archivo parece estar bien. Pero cuando abro el archivo en Excel O TextEdit, empiezo a ver personajes extraños como
â € ", â €, â € œ
aparecer en el texto. Básicamente, veo los personajes que se describen aquí: http://digwp.com/2011/07/clean-up-weird-characters-in-database/
Leí que este tipo de problema puede surgir cuando la configuración de codificación de la base de datos está configurada a la incorrecta. PERO, la base de datos que estoy usando está configurada para usar la codificación UTF8. Y cuando depuro a través de los códigos JS que crean el archivo CSV, el texto parece normal. (Esto podría ser una habilidad de Chrome y menos capacidad)
Me siento frustrado porque lo único que estoy aprendiendo de mi búsqueda en línea es que podría haber muchas razones por las cuales la codificación no funciona, no estoy seguro de cuál es la parte defectuosa (así que perdónenme porque inicialmente etiqueto muchas cosas) y nada de lo que intenté arrojó nueva luz sobre mi problema.
Como referencia, aquí está el fragmento de JavaScript que crea el archivo CSV.
$(document).ready(function() {
var csvData = <%= raw to_csv(@view_scope, clicks_post).as_json %>;
var csvContent = "data:text/csv;charset=utf-8,";
csvData.forEach(function(infoArray, index){
var dataString = infoArray.join(",");
csvContent += dataString+ "/n";
});
var encodedUri = encodeURI(csvContent);
var button = $(''<a>'');
button.text(''Download CSV'');
button.addClass("button right");
button.attr(''href'', encodedUri);
button.attr(''target'',''_blank'');
button.attr(''download'',''<%=title%>_25_posts.csv'');
$("#<%=title%>_download_action").append(button);
});
A Excel le gusta Unicode en UTF-16 LE con codificación BOM . Imprima la BOM correcta ( FF FE
) y luego convierta todos sus datos de UTF-8 a UTF-16 LE.
Windows usa UTF-16 LE internamente, por lo que algunas aplicaciones funcionan mejor con UTF-16 que con UTF-8.
No he intentado hacer eso en JS, pero hay varias secuencias de comandos en la web para convertir UTF-8 en UTF-16. La conversión entre variaciones UTF es bastante fácil y solo requiere una docena de líneas.
Como @jlarson se actualizó con la información de que Mac era el mayor culpable, podríamos obtener más información. Office para Mac tiene, al menos 2011 y viceversa, soporte bastante pobre para leer formatos Unicode al importar archivos.
El soporte para UTF-8 parece casi inexistente, he leído unos pocos comentarios sobre cómo funciona, mientras que la mayoría dice que no. Lamentablemente, no tengo ninguna Mac para probar. Entonces otra vez: los archivos en sí deberían estar bien como UTF-8, pero la importación detiene el proceso.
Escribió una prueba rápida en Javascript para exportar porcentaje escapado de UTF-16 little y big endian, con / sin lista de materiales, etc.
El código probablemente debería ser refactorizado, pero debería estar bien para la prueba. Podría funcionar mejor que UTF-8. Por supuesto, esto también suele significar transferencias de datos más grandes, ya que cualquier glifo es de dos o cuatro bytes.
Puedes encontrar un violín aquí:
Tenga en cuenta que no maneja CSV de ninguna manera en particular. Está principalmente destinado a la conversión pura a URL de datos que tiene UTF-8, UTF-16 big / little endian y +/- BOM. Hay una opción en el violín para reemplazar las comas con pestañas, pero creo que sería una solución bastante hackosa y frágil si funciona.
Por lo general, usa como:
// Initiate
encoder = new DataEnc({
mime : ''text/csv'',
charset: ''UTF-16BE'',
bom : true
});
// Convert data to percent escaped text
encoder.enc(data);
// Get result
var result = encoder.pay();
Hay dos propiedades de resultado del objeto:
1.) encoder.lead
Este es el tipo de mimo, juego de caracteres, etc. para la URL de datos. Construido a partir de opciones pasadas al inicializador, o uno también puede decir .config({ ... new conf ...}).intro()
para volver a compilar.
data:[<MIME-type>][;charset=<encoding>][;base64]
Puede especificar base64 , pero no hay conversión base64 (al menos no tan lejos).
2.) encoder.buf
Esta es una cadena con el porcentaje de datos escapados.
La función .pay()
simplemente devuelve 1.) y 2.) como uno.
Código principal:
function DataEnc(a) {
this.config(a);
this.intro();
}
/*
* http://www.iana.org/assignments/character-sets/character-sets.xhtml
* */
DataEnc._enctype = {
u8 : [''u8'', ''utf8''],
// RFC-2781, Big endian should be presumed if none given
u16be : [''u16'', ''u16be'', ''utf16'', ''utf16be'', ''ucs2'', ''ucs2be''],
u16le : [''u16le'', ''utf16le'', ''ucs2le'']
};
DataEnc._BOM = {
''none'' : '''',
''UTF-8'' : ''%ef%bb%bf'', // Discouraged
''UTF-16BE'' : ''%fe%ff'',
''UTF-16LE'' : ''%ff%fe''
};
DataEnc.prototype = {
// Basic setup
config : function(a) {
var opt = {
charset: ''u8'',
mime : ''text/csv'',
base64 : 0,
bom : 0
};
a = a || {};
this.charset = typeof a.charset !== ''undefined'' ?
a.charset : opt.charset;
this.base64 = typeof a.base64 !== ''undefined'' ? a.base64 : opt.base64;
this.mime = typeof a.mime !== ''undefined'' ? a.mime : opt.mime;
this.bom = typeof a.bom !== ''undefined'' ? a.bom : opt.bom;
this.enc = this.utf8;
this.buf = '''';
this.lead = '''';
return this;
},
// Create lead based on config
// data:[<MIME-type>][;charset=<encoding>][;base64],<data>
intro : function() {
var
g = [],
c = this.charset || '''',
b = ''none''
;
if (this.mime && this.mime !== '''')
g.push(this.mime);
if (c !== '''') {
c = c.replace(/[-/s]/g, '''').toLowerCase();
if (DataEnc._enctype.u8.indexOf(c) > -1) {
c = ''UTF-8'';
if (this.bom)
b = c;
this.enc = this.utf8;
} else if (DataEnc._enctype.u16be.indexOf(c) > -1) {
c = ''UTF-16BE'';
if (this.bom)
b = c;
this.enc = this.utf16be;
} else if (DataEnc._enctype.u16le.indexOf(c) > -1) {
c = ''UTF-16LE'';
if (this.bom)
b = c;
this.enc = this.utf16le;
} else {
if (c === ''copy'')
c = '''';
this.enc = this.copy;
}
}
if (c !== '''')
g.push(''charset='' + c);
if (this.base64)
g.push(''base64'');
this.lead = ''data:'' + g.join('';'') + '','' + DataEnc._BOM[b];
return this;
},
// Deliver
pay : function() {
return this.lead + this.buf;
},
// UTF-16BE
utf16be : function(t) { // U+0500 => %05%00
var i, c, buf = [];
for (i = 0; i < t.length; ++i) {
if ((c = t.charCodeAt(i)) > 0xff) {
buf.push((''00'' + (c >> 0x08).toString(16)).substr(-2));
buf.push((''00'' + (c & 0xff).toString(16)).substr(-2));
} else {
buf.push(''00'');
buf.push((''00'' + (c & 0xff).toString(16)).substr(-2));
}
}
this.buf += ''%'' + buf.join(''%'');
// Note the hex array is returned, not string with ''%''
// Might be useful if one want to loop over the data.
return buf;
},
// UTF-16LE
utf16le : function(t) { // U+0500 => %00%05
var i, c, buf = [];
for (i = 0; i < t.length; ++i) {
if ((c = t.charCodeAt(i)) > 0xff) {
buf.push((''00'' + (c & 0xff).toString(16)).substr(-2));
buf.push((''00'' + (c >> 0x08).toString(16)).substr(-2));
} else {
buf.push((''00'' + (c & 0xff).toString(16)).substr(-2));
buf.push(''00'');
}
}
this.buf += ''%'' + buf.join(''%'');
// Note the hex array is returned, not string with ''%''
// Might be useful if one want to loop over the data.
return buf;
},
// UTF-8
utf8 : function(t) {
this.buf += encodeURIComponent(t);
return this;
},
// Direct copy
copy : function(t) {
this.buf += t;
return this;
}
};
Respuesta anterior:
No tengo ninguna configuración para replicar la suya, pero si su caso es el mismo que @jlarson, entonces el archivo resultante debería ser correcto.
Esta respuesta se volvió algo larga (¿tema divertido que dices?) , Pero discuten varios aspectos en torno a la pregunta, qué es lo que (probablemente) está sucediendo y cómo comprobar realmente lo que está sucediendo de varias maneras.
TL; DR:
Es probable que el texto se importe como ISO-8859-1, Windows-1252 o similar, y no como UTF-8. Forzar la aplicación para leer el archivo como UTF-8 usando importación u otros medios.
PD: El UniSearcher es una buena herramienta para tener disponible en este viaje.
El camino más largo
La forma "más fácil" de estar 100% seguro de lo que estamos viendo es usar un editor hexadecimal en el resultado. Alternativamente, use hexdump
, xxd
o similar desde la línea de comando para ver el archivo. En este caso, la secuencia de bytes debe ser la de UTF-8 tal como se entrega desde el script.
Como ejemplo, si tomamos el script de jlarson , toma los data
Array :
data = [''name'', ''city'', ''state''],
[''/u0500/u05E1/u0E01/u1054'', ''seattle'', ''washington'']
Este se fusionó en la cadena:
name,city,state<newline>
/u0500/u05E1/u0E01/u1054,seattle,washington<newline>
que se traduce por Unicode en:
name,city,state<newline>
Ԁסกၔ,seattle,washington<newline>
Como UTF-8 usa ASCII como base (los bytes con el bit más alto no establecido son los mismos que en ASCII), la única secuencia especial en los datos de prueba es "Ԁ ס ก which" que a su vez es:
Code-point Glyph UTF-8
----------------------------
U+0500 Ԁ d4 80
U+05E1 ס d7 a1
U+0E01 ก e0 b8 81
U+1054 ၔ e1 81 94
Mirando el volcado hexadecimal del archivo descargado:
0000000: 6e61 6d65 2c63 6974 792c 7374 6174 650a name,city,state.
0000010: d480 d7a1 e0b8 81e1 8194 2c73 6561 7474 ..........,seatt
0000020: 6c65 2c77 6173 6869 6e67 746f 6e0a le,washington.
En la segunda línea encontramos d480 d7a1 e0b8 81e1 8194
que coincide con lo anterior:
0000010: d480 d7a1 e0b8 81 e1 8194 2c73 6561 7474 ..........,seatt
| | | | | | | | | | | | | |
+-+-+ +-+-+ +--+--+ +--+--+ | | | | | |
| | | | | | | | | |
Ԁ ס ก ၔ , s e a t t
Ninguno de los otros personajes está destrozado tampoco.
Haz pruebas similares si quieres. El resultado debería ser similar.
Por muestra provista —, â€, “
También podemos echarle un vistazo a la muestra provista en la pregunta. Es probable suponer que el texto está representado en Excel / TextEdit por la página de códigos 1252.
Para citar Wikipedia en Windows-1252:
Windows-1252 o CP-1252 es una codificación de caracteres del alfabeto latino, utilizada por defecto en los componentes heredados de Microsoft Windows en inglés y en otros idiomas occidentales. Es una versión dentro del grupo de páginas de códigos de Windows. En los paquetes LaTeX, se conoce como "ansinew".
Recuperando los bytes originales
Para traducirlo nuevamente a su forma original, podemos ver el diseño de la página de códigos , de la cual obtenemos:
Character: <â> <€> <”> <,> < > <â> <€> < > <,> < > <â> <€> <œ>
U.Hex : e2 20ac 201d 2c 20 e2 20ac 9d 2c 20 e2 20ac 153
T.Hex : e2 80 94 2c 20 e2 80 9d* 2c 20 e2 80 9c
-
U
es la abreviatura de Unicode -
T
es la abreviatura de Traducido
Por ejemplo:
â => Unicode 0xe2 => CP-1252 0xe2
” => Unicode 0x201d => CP-1252 0x94
€ => Unicode 0x20ac => CP-1252 0x80
Casos especiales como 9d
no tienen un punto de código correspondiente en CP-1252, estos simplemente copiamos directamente.
Nota: Si se observa una cadena alterada copiando el texto en un archivo y haciendo un volcado hexadecimal, guarde el archivo con, por ejemplo, codificación UTF-16 para obtener los valores Unicode como se representan en la tabla. Por ejemplo, en Vim:
set fenc=utf-16
# Or
set fenc=ucs-2
Bytes a UTF-8
Luego combinamos el resultado, la línea T.Hex
, en UTF-8. En las secuencias UTF-8, los bytes están representados por un byte inicial que nos dice cuántos bytes subsiguientes forman el glifo . Por ejemplo, si un byte tiene el valor binario 110x xxxx
, sabemos que este byte y el siguiente representan un punto de código. Un total de dos. 1110 xxxx
nos dice que son tres y así sucesivamente. Los valores ASCII no tienen el bit alto configurado, por lo tanto, cualquier coincidencia de bytes 0xxx xxxx
es independiente. Un total de un byte.
0xe2 = 1110 0010bin => 3 bytes => 0xe28094 (em-dash) — 0x2c = 0010 1100bin => 1 byte => 0x2c (comma) , 0x2c = 0010 0000bin => 1 byte => 0x20 (space) 0xe2 = 1110 0010bin => 3 bytes => 0xe2809d (right-dq) ” 0x2c = 0010 1100bin => 1 byte => 0x2c (comma) , 0x2c = 0010 0000bin => 1 byte => 0x20 (space) 0xe2 = 1110 0010bin => 3 bytes => 0xe2809c (left-dq) “
Conclusión; La secuencia original UTF-8 fue:
—, ”, “
Mangling de nuevo
También podemos hacer lo contrario. La cadena original como bytes:
UTF-8: e2 80 94 2c 20 e2 80 9d 2c 20 e2 80 9c
Valores correspondientes en cp-1252 :
e2 => â
80 => €
94 => ”
2c => ,
20 => <space>
...
y así sucesivamente, resultado:
—, â€, “
Importación a MS Excel
En otras palabras: el tema en cuestión podría ser cómo importar archivos de texto UTF-8 en MS Excel y algunas otras aplicaciones. En Excel esto se puede hacer de varias maneras.
- Método uno:
No guarde el archivo con una extensión reconocida por la aplicación, como .csv
o .txt
, pero omítalo por completo o invente algo.
Como ejemplo, guarde el archivo como "testfile"
archivo de "testfile"
, sin extensión. Luego, en Excel, abra el archivo, confirme que realmente queremos abrir este archivo, y listo nos servirá con la opción de codificación. Seleccione UTF-8 y el archivo debe leerse correctamente.
- Método dos:
Use datos de importación en lugar de abrir el archivo. Algo como:
Data -> Import External Data -> Import Data
Seleccione la codificación y proceda.
Compruebe que Excel y la fuente seleccionada realmente sean compatibles con el glifo
También podemos probar el soporte de fuentes para los caracteres Unicode usando el portapapeles, a veces más amigable. Por ejemplo, copie el texto de esta página en Excel:
Si existe soporte para los puntos de código, el texto debería mostrarse bien.
Linux
En Linux, que es principalmente UTF-8 en el mundo del usuario, esto no debería ser un problema. Al usar Libre Office Calc, Vim, etc., se muestran los archivos procesados correctamente.
Por qué funciona (o debería)
encodeURI desde los estados de especificación, (también lea sec-15.1.3 ):
La función encodeURI calcula una nueva versión de un URI en el que cada instancia de ciertos caracteres se reemplaza por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter.
Simplemente podemos probar esto en nuestra consola, por ejemplo diciendo:
>> encodeURI(''Ԁסกၔ,seattle,washington'')
<< "%D4%80%D7%A1%E0%B8%81%E1%81%94,seattle,washington"
Como registramos, las secuencias de escape son iguales a las del volcado hexadecimal que se muestra arriba:
%D4%80%D7%A1%E0%B8%81%E1%81%94 (encodeURI in log)
d4 80 d7 a1 e0 b8 81 e1 81 94 (hex-dump of file)
o, probando un código de 4 bytes:
>> encodeURI('''')
<< "%F3%B1%80%81"
Si esto es no cumple
Si nada de esto aplica, podría ayudar si agregó
- Muestra de la entrada esperada frente a la salida destruida, (copiar pegar).
- Ejemplo de volcado hexadecimal de datos originales frente a archivo de resultados.
Estaba teniendo un problema similar con los datos que fueron insertados en Javascript desde una lista Sharepoint. Resultó ser algo llamado "Zero Width Space" y se mostraba como "cuando se introdujo en Excel". Aparentemente, Sharepoint los inserta a veces cuando un usuario da ''backspace''.
Los reemplacé con este quickfix:
var mystring = myString.replace(//u200B/g,'''');
Parece que puedes tener otros personajes ocultos allí. Encontré el punto de código para el carácter de ancho cero en el mío mirando la cadena de salida en el inspector de Chrome. El inspector no pudo representar el personaje, por lo que lo reemplazó con un punto rojo. Cuando pasa el mouse sobre ese punto rojo, le da el punto de código (por ejemplo, B) y puede sub en los diversos puntos de código a los caracteres invisibles y eliminarlos de esa manera.
Me encontré con exactamente esto ayer. Desarrollaba un botón que exporta los contenidos de una tabla HTML como descarga CSV. La funcionalidad del botón en sí es casi idéntica a la suya: al hacer clic, leo el texto de la tabla y creo un URI de datos con el contenido CSV.
Cuando intenté abrir el archivo resultante en Excel, estaba claro que el símbolo "£" se estaba leyendo incorrectamente. La representación de 2 bytes UTF-8 se estaba procesando como ASCII, lo que daba como resultado un carácter de basura no deseado. Algunos de Google indicaron que este era un problema conocido con Excel.
Intenté agregar la marca de orden de bytes al comienzo de la cadena: Excel simplemente lo interpretó como datos ASCII. Luego intenté varias cosas para convertir la cadena UTF-8 en ASCII (como csvData.replace(''/u00a3'', ''/xa3'')
), pero descubrí que cada vez que los datos se fuerzan a una cadena de JavaScript se convertirá en UTF -8 de nuevo. El truco es convertirlo en binario y luego Base64 codificarlo sin convertirlo nuevamente en una cadena en el camino.
Ya tenía CryptoJS en mi aplicación (utilizado para la autenticación HMAC contra una API REST) y pude usarlo para crear una secuencia de bytes codificados en ASCII a partir de la cadena original, luego Base64 la codificó y creo un URI de datos. Esto funcionó y el archivo resultante cuando se abre en Excel no muestra ningún carácter no deseado.
El bit de código esencial que hace la conversión es:
var csvHeader = ''data:text/csv;charset=iso-8859-1;base64,''
var encodedCsv = CryptoJS.enc.Latin1.parse(csvData).toString(CryptoJS.enc.Base64)
var dataURI = csvHeader + encodedCsv
Donde csvData
es tu cadena de CSV.
Probablemente haya formas de hacer lo mismo sin CryptoJS si no desea ingresar esa biblioteca, pero al menos muestra que es posible.
Podría ser un problema en la codificación de su servidor.
Podrías probar (suponiendo locale english US) si estás ejecutando Linux:
sudo locale-gen en_US en_US.UTF-8
dpkg-reconfigure locales
button.href = ''data:'' + mimeType + '';charset=UTF-8,%ef%bb%bf'' + encodedUri;
Esto debería funcionar