web-dev-qa-db-ger.com

Codierungsprobleme für UTF8 CSV-Datei beim Öffnen von Excel und TextEdit

Ich habe vor kurzem eine CSV-Download-Schaltfläche hinzugefügt, mit der Daten aus der Datenbank (Postgres) eines Arrays vom Server (Ruby on Rails) abgerufen und in eine CSV-Datei auf der Clientseite (Javascript, HTML5) umgewandelt werden. Ich teste gerade die CSV-Datei und stoße auf Codierungsprobleme. 

Wenn ich die CSV-Datei über "Weniger" anzeige, erscheint die Datei in Ordnung. Wenn ich jedoch die Datei in Excel OR TextEdit öffne, sehe ich merkwürdige Zeichen wie 

â € “, â €“

im Text erscheinen. Grundsätzlich sehe ich die Zeichen, die hier beschrieben werden: http://digwp.com/2011/07/clean-up-weird-characters-in-database/

Ich habe gelesen, dass diese Art von Problem auftreten kann, wenn die Einstellung für die Datenbankverschlüsselung falsch eingestellt ist. ABER, die Datenbank, die ich verwende, ist auf UTF8-Codierung eingestellt. Wenn ich durch die JS-Codes debugge, die die CSV-Datei erstellen, erscheint der Text normal. (Dies könnte eine Chrome-Funktion sein und weniger.) 

Ich bin frustriert, weil das einzige, was ich von meiner Online-Suche lerne, ist, dass es viele Gründe gibt, warum die Kodierung nicht funktioniert. Ich bin mir nicht sicher, welches Teil fehlerhaft ist (entschuldigen Sie mich, da ich anfangs zahlreiche Dinge markiere). und nichts, das ich ausprobiert habe, hat mein Problem neu beleuchtet.

Hier sehen Sie das JavaScript-Snippet, mit dem die CSV-Datei erstellt wird! 

$(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);
});
22
Ji Mun

Als @jlarson mit der Information aktualisiert wurde, dass Mac der größte Schuldige war, könnten wir einige weitere bekommen. Office für Mac unterstützt das Lesen von Unicode-Formaten beim Importieren von Dateien zumindest ab 2011 nur unzureichend.

Die Unterstützung für UTF-8 scheint so gut wie nicht zu existieren, ich habe ein paar winzige Kommentare darüber gelesen, dass es funktioniert, während die Mehrheit sagt, dass dies nicht der Fall ist. Leider habe ich keinen Mac zum Testen. Also nochmal: Die Dateien selbst sollten als UTF-8 OK sein, aber der Import hält den Prozess an.

Schnelltest in Javascript für den Export von UTF-16-Little- und Big-Endian-Dateien mit oder ohne Stückliste geschrieben.

Code sollte wahrscheinlich überarbeitet werden, aber zum Testen in Ordnung sein. Es könnte besser funktionieren als UTF-8. Natürlich bedeutet dies normalerweise auch größere Datenübertragungen, da jede Glyphe aus zwei oder vier Bytes besteht.

Eine Geige finden Sie hier:

nicode export sample Fiddle

Beachten Sie, dass CSV in keiner bestimmten Weise behandelt . Es ist hauptsächlich für die reine Konvertierung in Daten-URLs mit UTF-8, UTF-16 Big/Little Endian und +/- BOM gedacht. Es gibt eine Option in der Geige , um Kommas durch Tabulatoren zu ersetzen, - aber glauben Sie, dass dies eine ziemlich hackige und fragile Lösung wäre, wenn es funktioniert.


Verwenden Sie normalerweise wie folgt:

// 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();

Es gibt zwei Ergebniseigenschaften des Objekts:

1.) encoder.lead

Dies ist der MIME-Typ, Zeichensatz usw. für die Daten-URL. Errichtet aus Optionen, die an den Initialisierer übergeben wurden, oder man kann auch .config({ ... new conf ...}).intro() zum Neuerstellen sagen.

data:[<MIME-type>][;charset=<encoding>][;base64]

Sie können base64 angeben, aber es gibt keine base64 -Konvertierung (zumindest nicht so weit).

2.) encoder.buf

Dies ist eine Zeichenfolge mit den prozentualen Escape-Daten.

Die Funktion .pay() gibt einfach 1.) und 2.) als eins zurück.


Haupt code:


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;
    }
};

Vorherige Antwort:


Ich habe keine Einrichtung, um Ihre zu replizieren, aber wenn Ihr Fall mit @jlarson identisch ist, sollte die resultierende Datei korrekt sein.

Diese Antwort wurde etwas lang (lustiges Thema, das Sie sagen?) , aber diskutieren Sie verschiedene Aspekte rund um die Frage, was (wahrscheinlich) passiert und wie Sie tatsächlich überprüfen, was los ist auf verschiedene Arten.

TL; DR:

Der Text wird wahrscheinlich als ISO-8859-1, Windows-1252 oder dergleichen und nicht als UTF-8 importiert. Erzwingen Sie, dass die Anwendung die Datei als UTF-8 liest, indem Sie den Import oder eine andere Methode verwenden.


PS: Der UniSearcher ist ein nettes Tool, das Sie auf dieser Reise zur Verfügung haben.

Der weite Weg

Der "einfachste" Weg, um 100% sicher zu sein, was wir suchen, ist die Verwendung eines Hex-Editors für das Ergebnis. Verwenden Sie alternativ hexdump, xxd oder ähnliches in der Befehlszeile, um die Datei anzuzeigen. In diesem Fall sollte die Byte-Sequenz die von UTF-8 sein, wie sie vom Skript geliefert wird.

Wenn wir als Beispiel das Skript von jlarson verwenden, wird das Array data verwendet:

data = ['name', 'city', 'state'],
       ['\u0500\u05E1\u0E01\u1054', 'seattle', 'washington']

Dieser wird in die Zeichenfolge eingefügt:

 name,city,state<newline>
 \u0500\u05E1\u0E01\u1054,seattle,washington<newline>

was übersetzt von Unicode zu:

 name,city,state<newline>
 Ԁסกၔ,seattle,washington<newline>

Da UTF-8 ASCII als Basis verwendet (Bytes mit dem höchsten Bit , die nicht gesetzt sind wie in ASCII), ist die einzige spezielle Sequenz in den Testdaten " Ԁ ס ก ก "was wiederum ist:

Code-point  Glyph      UTF-8
----------------------------
    U+0500    Ԁ        d4 80
    U+05E1    ס        d7 a1
    U+0E01    ก     e0 b8 81
    U+1054    ၔ     e1 81 94

Betrachten Sie den Hex-Dump der heruntergeladenen Datei:

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.

In der zweiten Zeile finden wir d480 d7a1 e0b8 81e1 8194, der mit dem obigen übereinstimmt:

0000010: d480  d7a1  e0b8 81  e1 8194 2c73 6561 7474  ..........,seatt
         |   | |   | |     |  |     |  | |  | |  | |
         +-+-+ +-+-+ +--+--+  +--+--+  | |  | |  | |
           |     |      |        |     | |  | |  | |
           Ԁ     ס      ก        ၔ     , s  e a  t t

Keines der anderen Charaktere ist entstellt.

Machen Sie ähnliche Tests, wenn Sie möchten. Das Ergebnis sollte ähnlich sein.


Durch die Probe zur Verfügung gestellt —, â€, “

Wir können uns auch das in der Frage angegebene Beispiel ansehen. Es ist wahrscheinlich anzunehmen, dass der Text in Excel/TextEdit durch die Codepage 1252 dargestellt wird.

So zitieren Sie Wikipedia unter Windows-1252:

Windows-1252 oder CP-1252 ist eine Zeichencodierung des lateinischen Alphabets, die standardmäßig in den älteren Komponenten von Microsoft Windows in Englisch und einigen anderen westlichen Sprachen verwendet wird. Es ist eine Version innerhalb der Gruppe der Windows-Codepages. In LaTeX-Paketen wird es als "ansinew" bezeichnet.

Abrufen der ursprünglichen Bytes

Um es wieder in seine ursprüngliche Form zu übersetzen, können wir uns das Codepage-Layout ansehen, von dem wir erhalten:

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 steht für Unicode
  • T steht für Übersetzt

Zum Beispiel:

â => Unicode 0xe2   => CP-1252 0xe2
” => Unicode 0x201d => CP-1252 0x94
€ => Unicode 0x20ac => CP-1252 0x80

Sonderfälle wie 9d haben in CP-1252 keinen entsprechenden Code-Punkt, diese kopieren wir einfach direkt.

Hinweis: Wenn Sie sich eine verstümmelte Zeichenfolge ansehen, indem Sie den Text in eine Datei kopieren und einen Hex-Dump ausführen, speichern Sie die Datei beispielsweise mit UTF-16-Codierung, um die in der Tabelle dargestellten Unicode-Werte zu erhalten. Z.B. in Vim:

set fenc=utf-16
# Or
set fenc=ucs-2

Bytes zu UTF-8

Wir kombinieren dann das Ergebnis, die Zeile T.Hex, in UTF-8. In UTF-8-Sequenzen werden die Bytes durch ein führendes Byte, das angibt, wie viele nachfolgende Bytes die Glyphe bilden dargestellt. Wenn zum Beispiel ein Byte den Binärwert 110x xxxx hat, wissen wir, dass dieses Byte und das nächste einen Codepunkt darstellen. Insgesamt zwei. 1110 xxxx sagt uns, dass es drei sind und so weiter. Bei ASCII -Werten ist das High-Bit nicht gesetzt, da jedes Byte, das mit 0xxx xxxx übereinstimmt, eigenständig ist. Insgesamt ein Byte.

0xe2 = 1110 0010behälter => 3 Bytes => 0xe28094 (Bindestrich) - 
 0x2c = 0010 1100behälter => 1 Byte => 0x2c (Komma), 
 0x2c = 0010 0000behälter => 1 Byte => 0x20 (Leerzeichen) 
 0xe2 = 1110 0010behälter => 3 Bytes => 0xe2809d (rechts-dq) ”
 0x2c = 0010 1100behälter => 1 Byte => 0x2c (Komma), 
 0x2c = 0010 0000behälter => 1 Byte => 0x20 (Leerzeichen) 
 0xe2 = 1110 0010behälter => 3 Bytes => 0xe2809c (left-dq) “

Fazit; Die ursprüngliche UTF-8-Zeichenfolge lautete:

—, ”, “

Zerfleischen Sie es zurück

Wir können auch das Gegenteil tun. Die ursprüngliche Zeichenfolge als Bytes:

UTF-8: e2 80 94 2c 20 e2 80 9d 2c 20 e2 80 9c

Entsprechende Werte in cp-1252 :

e2 => â
80 => €
94 => ”
2c => ,
20 => <space>
...

und so weiter, Ergebnis:

—, â€, “

Import nach MS Excel

Mit anderen Worten: Das Problem könnte sein, wie UTF-8-Textdateien in MS Excel und in einige andere Anwendungen importiert werden können. In Excel kann dies auf verschiedene Arten erfolgen.

  • Methode eins:

Speichern Sie die Datei nicht mit einer von der Anwendung erkannten Erweiterung wie .csv oder .txt, sondern lassen Sie sie vollständig weg oder erfinden Sie etwas.

Speichern Sie als Beispiel die Datei als "testfile" ohne Erweiterung. Öffnen Sie dann in Excel die Datei, und bestätigen Sie, dass Sie diese Datei tatsächlich öffnen möchten. voilà wird die Codierungsoption angezeigt. Wählen Sie UTF-8 und die Datei sollte korrekt gelesen werden.

  • Methode zwei:

Verwenden Sie Importdaten anstelle von geöffneten Dateien. So etwas wie:

Data -> Import External Data -> Import Data

Codierung auswählen und fortfahren.

Stellen Sie sicher, dass Excel und die ausgewählte Schriftart die Glyphe tatsächlich unterstützen

Wir können auch die Schriftartunterstützung für die Unicode-Zeichen testen, indem wir die manchmal freundlichere Zwischenablage verwenden. Kopieren Sie beispielsweise Text von dieser Seite in Excel:

Wenn Unterstützung für die Codepunkte vorhanden ist, sollte der Text gut dargestellt werden.


Linux

Unter Linux, bei dem es sich hauptsächlich um UTF-8 im Benutzerland handelt, sollte dies kein Problem sein. Mit Libre Office Calc, Vim usw. werden die Dateien korrekt gerendert.


Warum es funktioniert (oder sollte)

encodeURI aus den Spezifikationszuständen (lies auch sec-15.1. ):

Die Funktion encodeURI berechnet eine neue Version eines URI, in der jede Instanz bestimmter Zeichen durch eine, zwei, drei oder vier Escape-Sequenzen ersetzt wird, die die UTF-8-Codierung des Zeichens darstellen.

Wir können dies einfach in unserer Konsole testen, indem wir zum Beispiel sagen:

>> encodeURI('Ԁסกၔ,seattle,washington')
<< "%D4%80%D7%A1%E0%B8%81%E1%81%94,seattle,washington"

Wie wir registrieren, sind die Escape-Sequenzen gleich denen im Hex-Dump oben:

%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)

oder Testen eines 4-Byte-Codes:

>> encodeURI('????')
<< "%F3%B1%80%81"

Wenn dies nicht entspricht

Wenn nichts davon zutrifft, könnte es helfen, wenn Sie hinzugefügt haben

  1. Stichprobe der erwarteten Eingabe im Vergleich zur gestörten Ausgabe (Kopieren und Einfügen).
  2. Beispiel-Hex-Dump von Originaldaten gegen Ergebnisdatei.
36
user13500

Ich bin gestern genau darauf gestoßen. Ich habe eine Schaltfläche entwickelt, die den Inhalt einer HTML-Tabelle als CSV-Download exportiert. Die Funktionalität der Schaltfläche selbst ist fast identisch mit der von Ihnen - auf Klick lese ich den Text aus der Tabelle und erstelle einen Daten-URI mit dem CSV-Inhalt.

Beim Versuch, die resultierende Datei in Excel zu öffnen, wurde deutlich, dass das Symbol "£" falsch gelesen wurde. Die 2-Byte-UTF-8-Darstellung wurde als ASCII verarbeitet, was zu einem unerwünschten Abfallzeichen führte. Einige Google-Nachrichten zeigten, dass dies ein bekanntes Problem mit Excel war.

Ich habe versucht, die Bytereihenfolgenmarkierung am Anfang der Zeichenfolge hinzuzufügen - Excel interpretierte sie einfach als ASCII - Daten. Ich habe dann verschiedene Dinge versucht, um den UTF-8-String in ASCII (wie csvData.replace('\u00a3', '\xa3')) zu konvertieren, aber ich habe festgestellt, dass jedes Mal, wenn die Daten in einen JavaScript-String umgewandelt werden, UTF-8 wird. Der Trick besteht darin, es in eine Binärdatei zu konvertieren und dann mit Base64 zu codieren, ohne dabei eine Zeichenfolge zu erzeugen.

Ich hatte bereits CryptoJS in meiner App (wird für die HMAC-Authentifizierung gegen eine REST - API) verwendet, und ich konnte damit eine ASCII - codierte Bytefolge aus der ursprünglichen Zeichenfolge und dann Base64 codieren und erstellen Sie eine Daten-URI. Dies hat funktioniert und die resultierende Datei zeigt beim Öffnen in Excel keine unerwünschten Zeichen an.

Der wesentliche Code, der die Konvertierung durchführt, ist:

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

Dabei ist csvData Ihre CSV-Zeichenfolge.

Es gibt wahrscheinlich Möglichkeiten, dasselbe ohne CryptoJS zu tun, wenn Sie diese Bibliothek nicht mitbringen möchten, aber dies zeigt zumindest, dass dies möglich ist.

5
Rob Fletcher

Excel mag Unicode in UTF-16 LE mit BOM -Kodierung. Geben Sie das korrekte BOM (FF FE) aus und konvertieren Sie anschließend alle Ihre Daten von UTF-8 nach UTF-16 LE.

Windows verwendet intern UTF-16 LE, sodass einige Anwendungen mit UTF-16 besser funktionieren als mit UTF-8.

Ich habe nicht versucht, dies in JS zu tun, aber es gibt verschiedene Skripts im Web, um UTF-8 in UTF-16 zu konvertieren. Die Konvertierung zwischen UTF-Variationen ist ziemlich einfach und dauert nur ein Dutzend Zeilen.

3
Athari

Ich hatte ein ähnliches Problem mit Daten, die aus einer Sharepoint-Liste in Javascript abgerufen wurden. Es stellte sich heraus, dass es sich um ein Zeichen namens "Zero Width Space" handelt, das als angezeigt wurde, als es in Excel eingefügt wurde. Offensichtlich fügt Sharepoint diese manchmal ein, wenn ein Benutzer die Rücktaste drückt.

Ich habe sie mit diesem Quickfix ersetzt:

var mystring = myString.replace(/\u200B/g,'');

Es sieht so aus, als ob Sie andere versteckte Charaktere haben könnten. Ich habe den Codepunkt für das Zero-Width-Zeichen in meinem gefunden, indem ich den Ausgabestring im Chrome-Inspector anschaue. Der Inspektor konnte das Zeichen nicht rendern und ersetzte es durch einen roten Punkt. Wenn Sie mit der Maus über den roten Punkt fahren, erhalten Sie den Codepunkt (z. B.\u200B), und Sie können die verschiedenen Codepunkte einfach zu den unsichtbaren Zeichen wechseln und sie auf diese Weise entfernen.

2
Josh Abrams
button.href = 'data:' + mimeType + ';charset=UTF-8,%ef%bb%bf' + encodedUri;

das sollte den Trick tun

0
Alon Kogan

Es könnte ein Problem bei der Serverkodierung sein.

Sie können versuchen (vorausgesetzt, Sie verwenden ein Gebietsschema in den USA), wenn Sie Linux ausführen:

Sudo locale-gen en_US en_US.UTF-8
dpkg-reconfigure locales
0
goten