UDK: Speichern einer CSV-Datei (utf-8) nach "text/csv;charset=iso-8859-15"

Hallo,

ich muss ein JS-String in eine CSV-Datei mit dem MIME-Typ "text/csv;charset=iso-8859-15" zu speichern.

Dazu verwende ich den JS-Code:

var json = JSON.stringify(data),
	blob = new Blob([ data ], {
	  type: "text/csv;charset=iso-8859-15"
	}),
	url = window.URL.createObjectURL(blob);

Leider wird der Text trotzdem in utf-8 gespeichert oder übergeben. Es ist nicht möglich den Text auf einem Server mit PHP, Perl, … zu wandeln.

Zu diesem Thema oder der Problematik habe ich leider keinen brauchbaren Hinweis gefunden.

akzeptierte Antworten

  1. Hallo UDK,

    ich muss ein JS-String in eine CSV-Datei mit dem MIME-Typ "text/csv;charset=iso-8859-15" zu speichern.

    Leider wird der Text trotzdem in utf-8 gespeichert oder übergeben. Es ist nicht möglich den Text auf einem Server mit PHP, Perl, … zu wandeln.

    Die PHP-Funktion mb_convert_encoding leistet nicht das Verlangte?

    Bis demnächst
    Matthias

    --
    Du kannst das Projekt SELFHTML unterstützen,
    indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
    1. Danke für die Antwort.

      Die ganze HTML-Seite muss Off-Line im Browser funktionieren, also ohne einen Server, nur mit Javascript.

      1. Hallo UDK,

        Die ganze HTML-Seite muss Off-Line im Browser funktionieren, also ohne einen Server, nur mit Javascript.

        Ah, dann habe ich dein „Es ist nicht möglich“ falsch verstanden.

        Bis demnächst
        Matthias

        --
        Du kannst das Projekt SELFHTML unterstützen,
        indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
  2. Hallo,

    ich habe nichts gefunden, wie die Kodierung geändert werden könnte. Bei stackoverflow hat jemand den BOM für die Daten gesetzt, da seine SW wohl mit UTF-8 mit BOM klar kam:

    blob = new Blob(["\ufeff", csv_content]);
    

    Hast du es schon mal mit dem fileSaver probiert?

    Gruß
    Jürgen

  3. @@UDK

    Leider wird der Text trotzdem in utf-8 gespeichert oder übergeben.

    Wieso „leider“?

    MDN sagt USVString objects are encoded as UTF-8 und veweist auf die Spec: “Append the result of UTF-8 encoding s to bytes.”

    UTF-8, immer und überall.

    Die Frage sollte also sein: Warum verwendet ein Teil der Software im Jahre 2020 immer noch eine veraltete Zeichencodierung?

    😷 LLAP

    --
    „Sag mir, wie Du Deine Maske trägst, und ich sage Dir, ob Du ein Idiot bist.“ —@Ann_Waeltin
    1. Hallo Gunnar,

      Warum verwendet ein Teil der Software im Jahre 2020 immer noch eine veraltete Zeichencodierung?

      Wie hilft die Antwort auf diese Frage weiter, wenn solche Software vorliegt? Wenn es eine wirtschaftliche Option wäre, die Software zu ändern oder abzulösen, dann würde der OP das vermutlich tun.

      Wenn Du ein größeres Softwarepaket hast, das auf Singlebyte-Strings mit Codepage-Zeichensatz aufgebaut ist, dann ist eine Portierung nach UTF-8 ein Millionengrab. Einem an Business und RoI orientierten Management klar zu machen, dass hier eine teure technische Schuld lauert, die man unbedingt ablösen muss, ist so gut wie unmöglich. Ganz gleich, was wir Techniker sagen, es interessiert da oben einfach keinen. „Techniker, ach, die jammern doch ständig dass irgendwas veraltet wäre und ersetzt werden müsste. Unicode? Codepage? Hä? Wasndas? Egal, wie krieg ich nun meinen Bonus für dieses Jahr?“. Und wenn dann ein paar Jahre später gar nichts mehr geht, dann sind die Techniker schuld, weil sie überalterte Software bereitstellen, die wegen eines Haufens angeflanschter Adapter noch lebt, aber so komplex geworden ist, dass sie keiner mehr warten kann.

      Rolf

      --
      sumpsi - posui - obstruxi
  4. Hallo UDK,

    FALLS es Dir darum geht, die Daten nach Excel zu importieren: Das kann auch CSV-Import mit UTF-8. Dafür brauchst Du das nicht.

    Im BLOB-Konstruktor kannst Du einen MIME-Typ angeben, aber der beschreibt nicht das Soll, sondern nennt Information zum Ist, das Du hineingibst.

    Was Dir bis vor ein paar Jahren geholfen hätte, wäre die TextEncoder Klasse gewesen. Aber die Arroganz des Web, die alles außer UTF-8 für unbrauchbar erklärt und auf Legacy pfeift, hat die non-UTF8 Unterstützung dieser Klasse eliminiert. Ja, Gunnar, für das Web ist alles andere als UTF-8 unbrauchbar. Es gibt aber noch eine Welt jenseits des Web.

    Insofern hilft Dir nur Handarbeit. Überlege, welche Codepoints Du übersetzen können willst, und mach Dir eine Übersetzungstabelle für ISO-8859-15. Im einfachsten Fall ist das ein Array mit 256 Einträgen (aber nicht den Indexen 0-255), und du setzt an die Stelle, die dem UTF-8 Codepoint entspricht, den ISO-8859 Codewert des Zeichens. Dann rennst Du über deine Eingabe und erzeust für jedes UTF-8 Zeichen einen Eintrag in einem UInt8Array, und das steckst Du dann in deinen Blob. Auf diese Weise kannst Du auch Unicodezeichen, die in ISO-8859-15 nicht zu finden sind, auf ähnliche Ersatzzeichen mappen.

    Es gibt auch eine Library, die den Job tut, namens ICONV-LITE, aber die ist für node.js gemacht und muss erstmal mit Browserify für den Einsatz im Browser tauglich gemacht werden. Sagt die Doku - ich hab's noch nicht selbst getan. Und sie ist riesig (180KB).

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      danke für deine ausführliche Antwort für die wirtschaftliche Option, ich kann es nicht besser beschreiben.

      Zu meinem Problem:

      Die Site muss Offline laufen um Betriebsystem unabhängig eine strukturierte CSV-Datei zu überprüfen und ggf. zu korrigieren, um dann in ein System eingelesen werden, das diese Datei interpretiert und einliest. Standardmäßig wird die CSV-Datei mit Windowstools (Excel, Access, …) erzeugt. und die Schnittstellen sind schon ca. 20 Jahre alt.

      Deshalb werde ich wohl mir so eine Bibliothek/Übersetzungstabelle schreiben müssen (nur am Rande bemerkt das Einlesen funktioniert mit dem DataViewer).

      Dies war mein letzter Versuch:

        var csvString = CSV.toStr(HtmlTable.TableToArray());
        let charArray = [];
        csvString.split('').forEach((c) => {
      	   charArray.push(c.charCodeAt(0));
        });
        let toISO8859 = new TextDecoder('iso-8859-15');
        let bytes = new Uint8Array(charArray);
        csvString = toISO8859.decode(bytes);
      

      Vermutlich werde kurz vom Blob erzeugen die Ersetzungen vornehmem müssen.

      UDK

      1. Hallo UDK,

        nein, so funktioniert das nicht richtig. Es funktioniert näherungsweise, weil die Unicodezeichen 0x00 bis 0xff mit der ISO-8859-1 Codepage (Latin-1) übereinstimmen. Es gibt aber 8 Zeichen, die in ISO-8859-15 abweichen und im Unicode jenseits der 0xff liegen. Diese 8 Zeichen ersetzen 8 „unnötige“ Zeichen der ISO-8859-1 Codepage. Du musst also 16 Zeichen besonders behandeln:

        • Die Unicodezeichen 0xA4, 0xA6, 0xA8, 0xB4, 0xB8, 0xBC, 0xBD und 0xBE können nicht dargestellt werden (¤, ¦, ¨, ´, ¸, ¼, ½ und ¾), diese musst Du als "nicht darstellbares Zeichen" übersetzen. Ob es in ISO-8859 ein Normzeichen für "Nicht darstellbar" gibt, weiß ich nicht.
        • Die Unicodezeichen, die auf die ersetzten Zeichen abgebildet werden, musst Du übersetzen.
        Unicode Zeichen ISO-8859-15
        0x20ac 0xa4 (164)
        0x0160 Š 0xa6 (166)
        0x0161 š 0xa8 (168)
        0x017d Ž 0xb4 (180)
        0x017e ž 0xb8 (184)
        0x0152 Π0xbc (188)
        0x0153 œ 0xbd (189)
        0x0178 Ÿ 0xbe (190)

        Ich würde mir ein Array erzeugen, und erstmal die Indizes 0-255 mit den Werten 0-255 füllen.

        Danach wird feingetuned. An den in der Tabelle genannten ISO-8859-15 Indexen trägst Du undefined ein. Und an den in der Unicode-Spalte gannten Indexen den ISO-8859-15 Wert.

        Nun kannst Du deinen String Codepoint für Codepoint durchgehen. Erstmal legst Du Dir ein UInt8Array in der Länge wie dein String an, und gehst dann Zeichen für Zeichen durch. Den Codepoint an Stelle i schlägst Du im Übersetzungsarray nach. Findest Du einen Wert (also nicht undefined), trägst Du ihn an Stelle i im UInt8Array ein. Andernfalls ist das Zeichen nicht übersetzbar und Du musst zumindest ein Sonderzeichen für „Nicht darstellbares Zeichen“ eintragen.

        Das UInt8Array steckst Du in einen Blob und kannst eine ObjectURL draus machen.

        Rolf

        --
        sumpsi - posui - obstruxi
      2. Hallo UDK,

        ich habe mal was gefiddelt.

        Die charset-Angabe muss ISO-8859-15 heißen, zusammen mit text/plain wird der Text dann korrekt im Browser angezeigt wenn man auf den Link klickt. Dank der Dark-Theme-Fanatiker bei JSFiddle ist der Text dann schwarz auf dunkelgrau und kaum lesbar, du musst ihn markieren damit er lesbar wird.

        Der JavaScript-Code berücksichtigt auch Zeichen jenseits der BMP, z.B. die neueren Emojis im 0x1f3xx Bereich, da muss man nämlich 2 Zeichen weiterspringen. Das Zeichen 0x80 wird, zumindest vom Browser, als ein Fragezeichen in einem Rechteck dargestellt und bietet sich damit als "Unübersetzbar" Ersatzzeichen an.

        Beachte auch, dass Windows beispielsweise die Datei nicht korrekt anzeigt. Das operiert mit Latin-1, wo das Eurozeichen auf 0x80 versteckt wurde. ISO-8859-15 ist Latin-9.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo,

          Beachte auch, dass Windows [...] operiert mit Latin-1

          aka Windows-1252[1]. Und intern überwiegend mit UCS-2, einem Subset von UTF-16.

          Live long and pros healthy,
           Martin

          --
          Home is where my beer is.

          1. Zumindest ein auf Deutsch eingeschworenes Windows tut das. Auf andere Sprachregionen dressierte Windowse verwenden eventuell wieder andere Codierungen. ↩︎

          1. Hallo Martin,

            und intern überwiegend mit UCS-2

            JavaScript auch. Genau deshalb habe ich ja auch dies gefiedelt:

            i += (char > 65535 ? 2 : 1);
            

            Rolf

            --
            sumpsi - posui - obstruxi
      3. Hallo UDK,

        Standardmäßig wird die CSV-Datei mit Windowstools (Excel, Access, …) erzeugt.

        Huppala, das sehe ich jetzt erst. Uralte Excel-oder Access-Versionen werden Dir aber nicht ISO-8859-15 erzeugen, sondern Codepage 1252.

        Es kann durchaus sein, dass die Aufgabenstellung sich ganz anders darstellt als bisher gedacht. Dazu müsstest Du aber den Ablauf genauer beschreiben. Insbesondere wäre wichtig zu wissen, wie die Datei in den Browser hineinkommt, und was das für ein System ist, das die Datei dann weiterverarbeiten soll.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          dein Hinweis auf jsfiddle hat mich auf den richtigen Weg gebracht.

          // zur ganzen Darstellung der Lösung gehört auch das 
          // Einlesen der CSV-Dateien
          		   Connects.inputLoadCSV = {
          			 event: function(elem) {
          			   const file = elem.files[0];
          			   const textType = 
                      /(text\/csv)|(application\/vnd[.]ms-excel)/;
          
          			   if (file !== undefined && file.type.match(textType)) {
          				 const reader = new FileReader();
          				 reader.onload =
          				   /**
          					  *
          					  * @param {type} elem
          					  */
          				   function(elem) {
          				   var byteBuff = reader.result;
          				   var dataViewer = new DataView(byteBuff);
          				   var charArr = [];
          				   for(let i = 0; i < dataViewer.byteLength; i++) {
          					 charArr.push(dataViewer.getUint8(i));
          				   }
          				   var csvText = String.fromCharCode(...charArr);
          				   var csvArray = CSV.toArray(csvText);
          				   HtmlTable.create(csvArray);
          				 };
          				 reader.readAsArrayBuffer(file);
          			   }
          			   if (file !== undefined) {
          				 this.fileName.elem.textContent = file.name || '. . .';
          			   }
          			 }
          		   };
          
          		   Connects.btnSaveCSV = {
          			 event: function(elem) {
          			   var csvString = CSV.toStr(HtmlTable.TableToArray());
          			   let charArray = [];
          			   for (let i = 0; i < csvString.length; i++ ) {
          				 charArray.push(csvString.codePointAt(i));
          			   }
          			   let bytes = Uint8Array.from(charArray);
          			   let blob = new Blob([ bytes ], {
          				 type: "text/csv;charset=iso-8859-15"
          			   });
          			   let url = window.URL.createObjectURL(blob);
          			   saveData(url, "NewPOL.csv");
          			 }
          		   };
          
          		   function saveData(url, fileName) {
          			 var a = document.createElement("a");
          			 document.body.appendChild(a);
          			 a.style = "display: none";
          			 a.href = url;
          			 a.download = fileName;
          			 a.click();
          			 window.URL.revokeObjectURL(url);
          			 document.body.removeChild(a);
          		   }
          

          Ich habe zu viele Umwege gemacht. Blob kann auch direkt ein Uint8Array verarbeiten, scheinbar wird dann die korrekte Codierung verwendet. Zu den Excel/Access-Versionen, die verhältnismäßig aktuell, es wird direkt eine csv-Datei erzeugt. Und bei meinen Tests verwende ich vorhandene Dateien und vergleiche die Originaldatei und erzeugte Datei.

          Vielen Dank an alle die geantwortet haben. Vielleicht hilft meine Lösung weiter.

          UDK