Fehler beim Ausführen von 'btoa' in 'Window': Die zu codierende Zeichenfolge enthält Zeichen außerhalb des Bereichs Latin1.

133

Der Fehler im Titel wird nach meinen Tests nur in Google Chrome ausgelöst. Ich bin base64 und codiere eine große XML-Datei, damit sie heruntergeladen werden kann:

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">");

this.loader ist iframe versteckt.

Dieser Fehler ist eigentlich eine ziemliche Änderung, da Google Chrome normalerweise beim btoaAnruf abstürzt . Mozilla Firefox hat hier keine Probleme, daher hängt das Problem mit dem Browser zusammen. Mir sind keine seltsamen Zeichen in der Datei bekannt. Eigentlich glaube ich, dass es keine Nicht-ASCII-Zeichen gibt.

F: Wie finde ich die problematischen Zeichen und ersetze sie, damit Chrome sich nicht mehr beschwert?

Ich habe versucht, Downloadify zu verwenden, um den Download zu starten, aber es funktioniert nicht. Es ist unzuverlässig und gibt keine Fehler aus, um das Debuggen zu ermöglichen.

Tomáš Zato - Monica wieder einsetzen
quelle

Antworten:

212

Wenn Sie UTF8 haben, verwenden Sie dies (funktioniert tatsächlich mit SVG-Quelle) wie:

btoa(unescape(encodeURIComponent(str)))

Beispiel:

 var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(markup)));
 var img = new Image(1, 1); // width, height values are optional params 
 img.src = imgsrc;

Wenn Sie diese base64 dekodieren müssen, verwenden Sie Folgendes:

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

Beispiel:

var str = "äöüÄÖÜçéèñ";
var b64 = window.btoa(unescape(encodeURIComponent(str)))
console.log(b64);

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

Hinweis: Wenn dies in Mobile-Safari funktionieren soll, müssen Sie möglicherweise den gesamten Leerraum aus den base64-Daten entfernen ...

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

Update 2017

Dieses Problem hat mich wieder nervt.
Die einfache Wahrheit ist, dass atob nicht wirklich mit UTF8-Strings umgeht - es ist nur ASCII.
Außerdem würde ich keine Bloatware wie js-base64 verwenden.
Aber webtoolkit hat eine kleine, nette und sehr wartbare Implementierung:

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info
*
**/
var Base64 = {

    // private property
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

    // public method for encoding
    , encode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length)
        {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2))
            {
                enc3 = enc4 = 64;
            }
            else if (isNaN(chr3))
            {
                enc4 = 64;
            }

            output = output +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        } // Whend 

        return output;
    } // End Function encode 


    // public method for decoding
    ,decode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length)
        {
            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64)
            {
                output = output + String.fromCharCode(chr2);
            }

            if (enc4 != 64)
            {
                output = output + String.fromCharCode(chr3);
            }

        } // Whend 

        output = Base64._utf8_decode(output);

        return output;
    } // End Function decode 


    // private method for UTF-8 encoding
    ,_utf8_encode: function (string)
    {
        var utftext = "";
        string = string.replace(/\r\n/g, "\n");

        for (var n = 0; n < string.length; n++)
        {
            var c = string.charCodeAt(n);

            if (c < 128)
            {
                utftext += String.fromCharCode(c);
            }
            else if ((c > 127) && (c < 2048))
            {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else
            {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        } // Next n 

        return utftext;
    } // End Function _utf8_encode 

    // private method for UTF-8 decoding
    ,_utf8_decode: function (utftext)
    {
        var string = "";
        var i = 0;
        var c, c1, c2, c3;
        c = c1 = c2 = 0;

        while (i < utftext.length)
        {
            c = utftext.charCodeAt(i);

            if (c < 128)
            {
                string += String.fromCharCode(c);
                i++;
            }
            else if ((c > 191) && (c < 224))
            {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else
            {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        } // Whend 

        return string;
    } // End Function _utf8_decode 

}

https://www.fileformat.info/info/unicode/utf8.htm

  • Für jedes Zeichen gleich oder unter 127 (hex 0x7F) ist die UTF-8-Darstellung ein Byte. Es sind nur die niedrigsten 7 Bits des vollständigen Unicode-Werts. Dies entspricht auch dem ASCII-Wert.

  • Bei Zeichen gleich oder unter 2047 (hex 0x07FF) ist die UTF-8-Darstellung auf zwei Bytes verteilt. Im ersten Byte werden die beiden hohen Bits gesetzt und das dritte Bit gelöscht (dh 0xC2 bis 0xDF). Im zweiten Byte wird das oberste Bit gesetzt und das zweite Bit gelöscht (dh 0x80 bis 0xBF).

  • Für alle Zeichen gleich oder größer als 2048, jedoch kleiner als 65535 (0xFFFF) ist die UTF-8-Darstellung auf drei Bytes verteilt.

Stefan Steiger
quelle
6
Kannst du das etwas näher erläutern? Ich bin total verloren
Muhammad Umer
Ich würde nur den Code ausführen, wenn ich du wäre. escapekonvertiert eine Zeichenfolge in eine Zeichenfolge, die nur URL-gültige Zeichen enthält. Das verhindert die Fehler.
Tomáš Zato - Wiedereinsetzung Monica
6
escapeund unescapewurden in JavaScript 1.5 veraltet und sollte man verwenden encodeURIComponentoder decodeURIComponentjeweils statt. Sie verwenden die veralteten und neuen Funktionen zusammen. Warum? Sehen: w3schools.com/jsref/jsref_escape.asp
Leif
2
@Leif: Dies funktioniert nur genau deshalb, weil Flucht und Flucht fehlerhaft sind (auf die gleiche Weise);)
Stefan Steiger
8
Hat noch jemand hier ein Webpack benutzt?
Avindra Goolcharan
18

Verwenden btoamit unescapeund encodeURIComponenthat bei mir nicht funktioniert. Das Ersetzen aller Sonderzeichen durch XML / HTML-Entitäten und das anschließende Konvertieren in die base64-Darstellung war für mich die einzige Möglichkeit, dieses Problem zu lösen. Etwas Code:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));
Italo Borssatto
quelle
1
Seit ich diese Frage gestellt habe, habe ich etwas über APIs gelernt, die für meine Arbeit vorgesehen sind. Wenn die zu konvertierende Zeichenfolge lang ist, verwenden Sie Blobobject, um die Konvertierung durchzuführen. Blobkann mit beliebigen Binärdaten umgehen.
Tomáš Zato - Wiedereinsetzung Monica
1
Ich bin mir nicht sicher über IE9. Aber ich denke, wenn Sie Dinge wie die clientseitige Base64-Konvertierung tun, erstellen Sie wahrscheinlich eine moderne Web-App, die früher oder später ohnehin moderne Funktionen benötigt. Es gibt auch eine Blob-Polyfüllung.
Tomáš Zato - Wiedereinsetzung Monica
1
@ItaloBorssatto Du bist eine Legende!
Codeepic
1
@ItaloBorssatto Es war die einzige Lösung, die für mich funktioniert hat. Ich brauchte es, um das d3-SVG-Diagramm zu greifen, es mit XMLSerializer zu serialisieren, es an btoa () zu übergeben (hier habe ich Ihre Lösung verwendet), um eine Base-64-codierte ASCII-Zeichenfolge zu erstellen, und es dann an das Image-Element zu übergeben Zeichnen Sie es dann in die Leinwand und exportieren Sie es, damit Sie ein Bild im Frontend herunterladen können. Eher komplizierte und hackige Lösung, die jedoch keine serverseitig gerenderten Diagramme erfordert, wenn Benutzer einige Grafiken herunterladen möchten. Wenn Sie interessiert sind, kann ich Ihnen einige Codebeispiele senden. Der Kommentar ist zu kurz für sie
Codeepic
1
@ItaloBorssatto <svg xmlns = " w3.org/2000/svg " viewBox = "0 0 1060 105" width = "1060" height = "105"> <path class = "domain" Stroke = "none" d = "M. -6,0,5H0,5V35,5H-6 "> <Zeilenstrich =" keine "x2 =" - 6 "y1 =" 0,5 "y2 =" 0,5 "Füllung =" keine "Strichbreite =" 1px "Schriftart- family = "serifenlos" font-size = "10px" /> <text fill = "rgb (196, 196, 196)" x = "- 9" y = "0.5" dy = "0.32em"> VogueEspana - Vogue España </ text> <rect class = "first bar" fill = "rgb (25, 244, 71)" x = "0" y = "8" width = "790" height = "18" /> </ g> </ svg> Ich schneide irrelevante Stücke aus. Der Schuldige ist Vogue España -> ñ verhindert, dass ein Bild in den Browser geladen wird.
Codeepic
15

Verwenden Sie stattdessen eine Bibliothek

Wir müssen das Rad nicht neu erfinden. Verwenden Sie einfach eine Bibliothek, um Zeit und Kopfschmerzen zu sparen.

js-base64

https://github.com/dankogai/js-base64 ist gut und ich bestätige, dass es Unicode sehr gut unterstützt.

Base64.encode('dankogai');  // ZGFua29nYWk=
Base64.encode('小飼弾');    // 5bCP6aO85by+
Base64.encodeURI('小飼弾'); // 5bCP6aO85by-

Base64.decode('ZGFua29nYWk=');  // dankogai
Base64.decode('5bCP6aO85by+');  // 小飼弾
// note .decodeURI() is unnecessary since it accepts both flavors
Base64.decode('5bCP6aO85by-');  // 小飼弾
Tyler Long
quelle
Dies ist eine gute Lösung, obwohl es für btoa wie ein Versehen erscheint, sich auf ASCII zu beschränken (obwohl die Atob-Dekodierung gut zu funktionieren scheint). Dies funktionierte für mich, nachdem einige der anderen Antworten dies nicht taten. Vielen Dank!
Für den Namen
9

Ich dachte nur, ich sollte mitteilen, wie ich das Problem tatsächlich gelöst habe und warum ich denke, dass dies das Richtige ist Lösung ist (vorausgesetzt, Sie optimieren nicht für alte Browser).

Konvertieren von Daten in dataURL ( data: ...)

var blob = new Blob(
              // I'm using page innerHTML as data
              // note that you can use the array
              // to concatenate many long strings EFFICIENTLY
              [document.body.innerHTML],
              // Mime type is important for data url
              {type : 'text/html'}
); 
// This FileReader works asynchronously, so it doesn't lag
// the web application
var a = new FileReader();
a.onload = function(e) {
     // Capture result here
     console.log(e.target.result);
};
a.readAsDataURL(blob);

Benutzer können Daten speichern

Abgesehen von der offensichtlichen Lösung - das Öffnen eines neuen Fensters mit Ihrer dataURL als URL - können Sie zwei weitere Dinge tun.

1. Verwenden Sie fileSaver.js

File Saver kann einen tatsächlichen fileSave-Dialog mit einem vordefinierten Dateinamen erstellen. Es kann auch auf den normalen dataURL-Ansatz zurückgreifen.

2. Verwenden Sie (experimentell) URL.createObjectURL

Dies ist ideal für die Wiederverwendung von Base64-codierten Daten. Es wird eine kurze URL für Ihre dataURL erstellt:

console.log(URL.createObjectURL(blob));
//Prints: blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42

Vergessen Sie nicht, die URL einschließlich des führenden blobPräfixes zu verwenden. Ich habe document.bodywieder verwendet:

Bildbeschreibung

Sie können diese kurze URL als AJAX-Ziel, <script>Quelle oder <a>href-Speicherort verwenden. Sie sind jedoch dafür verantwortlich, die URL zu zerstören:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
Tomáš Zato - Monica wieder einsetzen
quelle
Danke Kumpel, du hast meinen Tag gerettet :)
Sandeep Kumar
3

Als Ergänzung zu Stefan Steiger Antwort: (da es als Kommentar nicht gut aussieht)

String-Prototyp erweitern:

String.prototype.b64encode = function() { 
    return btoa(unescape(encodeURIComponent(this))); 
};
String.prototype.b64decode = function() { 
    return decodeURIComponent(escape(atob(this))); 
};

Verwendung:

var str = "äöüÄÖÜçéèñ";
var encoded = str.b64encode();
console.log( encoded.b64decode() );

HINWEIS:

Wie in den Kommentaren angegeben, wird die Verwendung unescapenicht empfohlen, da sie möglicherweise in Zukunft entfernt wird:

Warnung : Obwohl unescape () nicht streng veraltet ist (wie in "aus den Webstandards entfernt"), ist es in Anhang B des ECMA-262-Standards definiert, dessen Einführung lautet:… Alle darin angegebenen Sprachmerkmale und -verhalten Anhang hat eine oder mehrere unerwünschte Eigenschaften und würde in Ermangelung einer alten Verwendung aus dieser Spezifikation entfernt.

Hinweis: Verwenden Sie nicht unescape zum Dekodieren von URIs, sondern stattdessen decodeURI oder decodeURIComponent .

Lepe
quelle
6
Funktionen sehen gut aus, aber die Erweiterung der Basisprototypen ist eine schlechte Praxis.
timemachine3030
4
Javascript ist eine schlechte Praxis. Was ist noch ein Hack, danke.
rob5408
1
@ rob5408: Ich stimme Ihrer Aussage zwar grundsätzlich zu, aber Sie sollten wirklich vorsichtiger sein: Das Erweitern von Prototypen bricht jQuery (eine andere Bibliothek, die das Prinzip "nur noch ein Hack" verwendet)
Stefan Steiger
@StefanSteiger Gut zu wissen, danke für den Einblick.
rob5408
unescapewird bald veraltet sein gemäß MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Akansh
2

btoa () unterstützt nur Zeichen von String.fromCodePoint (0) bis String.fromCodePoint (255). Für Base64-Zeichen mit einem Codepunkt 256 oder höher müssen Sie diese vorher und nachher codieren / decodieren.

Und an diesem Punkt wird es schwierig ...

Jedes mögliche Zeichen ist in einer Unicode-Tabelle angeordnet. Die Unicode-Tabelle ist in verschiedene Ebenen unterteilt (Sprachen, mathematische Symbole usw.). Jedes Zeichen in einem Flugzeug hat eine eindeutige Codepunktnummer. Theoretisch kann die Anzahl beliebig groß werden.

Ein Computer speichert die Daten in Bytes (8 Bit, hexadezimal 0x00 - 0xff, binär 00000000 - 11111111, dezimal 0 - 255). Dieser Bereich wird normalerweise zum Speichern von Basiszeichen verwendet (Bereich Latin1).

Für Zeichen mit höherem Codepunkt als 255 existieren unterschiedliche Codierungen. JavaScript verwendet 16 Bit pro Zeichen (UTF-16), die Zeichenfolge mit dem Namen DOMString. Unicode kann Codepunkte bis zu 0x10fffff verarbeiten. Das bedeutet, dass eine Methode vorhanden sein muss, um mehrere Bits über mehrere Zellen hinweg zu speichern.

String.fromCodePoint(0x10000).length == 2

UTF-16 verwendet Ersatzpaare, um 20 Bit in zwei 16-Bit-Zellen zu speichern. Der erste höhere Ersatz beginnt mit 110110xxxxxxxxxx , der niedrigere zweite mit 110111xxxxxxxxxx . Unicode reservierte dafür eigene Flugzeuge: https://unicode-table.com/de/#high-surrogates

Verwenden Sie UTF-8, um Zeichen in standardisierten Bytes (Bereich Latin1) zu speichern .

Tut mir leid, das zu sagen, aber ich denke, es gibt keine andere Möglichkeit, diese Funktion selbst zu implementieren.

function stringToUTF8(str)
{
    let bytes = [];

    for(let character of str)
    {
        let code = character.codePointAt(0);

        if(code <= 127)
        {
            let byte1 = code;

            bytes.push(byte1);
        }
        else if(code <= 2047)
        {
            let byte1 = 0xC0 | (code >> 6);
            let byte2 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2);
        }
        else if(code <= 65535)
        {
            let byte1 = 0xE0 | (code >> 12);
            let byte2 = 0x80 | ((code >> 6) & 0x3F);
            let byte3 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3);
        }
        else if(code <= 2097151)
        {
            let byte1 = 0xF0 | (code >> 18);
            let byte2 = 0x80 | ((code >> 12) & 0x3F);
            let byte3 = 0x80 | ((code >> 6) & 0x3F);
            let byte4 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3, byte4);
        }
    }

    return bytes;
}

function utf8ToString(bytes, fallback)
{
    let valid = undefined;
    let codePoint = undefined;
    let codeBlocks = [0, 0, 0, 0];

    let result = "";

    for(let offset = 0; offset < bytes.length; offset++)
    {
        let byte = bytes[offset];

        if((byte & 0x80) == 0x00)
        {
            codeBlocks[0] = byte & 0x7F;

            codePoint = codeBlocks[0];
        }
        else if((byte & 0xE0) == 0xC0)
        {
            codeBlocks[0] = byte & 0x1F;

            byte = bytes[++offset];
            if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

            codeBlocks[1] = byte & 0x3F;

            codePoint = (codeBlocks[0] << 6) + codeBlocks[1];
        }
        else if((byte & 0xF0) == 0xE0)
        {
            codeBlocks[0] = byte & 0xF;

            for(let blockIndex = 1; blockIndex <= 2; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 12) + (codeBlocks[1] << 6) + codeBlocks[2];
        }
        else if((byte & 0xF8) == 0xF0)
        {
            codeBlocks[0] = byte & 0x7;

            for(let blockIndex = 1; blockIndex <= 3; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 18) + (codeBlocks[1] << 12) + (codeBlocks[2] << 6) + (codeBlocks[3]);
        }
        else
        {
            valid = false; break;
        }

        result += String.fromCodePoint(codePoint);
    }

    if(valid === false)
    {
        if(!fallback)
        {
            throw new TypeError("Malformed utf-8 encoding.");
        }

        result = "";

        for(let offset = 0; offset != bytes.length; offset++)
        {
            result += String.fromCharCode(bytes[offset] & 0xFF);
        }
    }

    return result;
}

function decodeBase64(text, binary)
{
    if(/[^0-9a-zA-Z\+\/\=]/.test(text)) { throw new TypeError("The string to be decoded contains characters outside of the valid base64 range."); }

    let codePointA = 'A'.codePointAt(0);
    let codePointZ = 'Z'.codePointAt(0);
    let codePointa = 'a'.codePointAt(0);
    let codePointz = 'z'.codePointAt(0);
    let codePointZero = '0'.codePointAt(0);
    let codePointNine = '9'.codePointAt(0);
    let codePointPlus = '+'.codePointAt(0);
    let codePointSlash = '/'.codePointAt(0);

    function getCodeFromKey(key)
    {
        let keyCode = key.codePointAt(0);

        if(keyCode >= codePointA && keyCode <= codePointZ)
        {
            return keyCode - codePointA;
        }
        else if(keyCode >= codePointa && keyCode <= codePointz)
        {
            return keyCode + 26 - codePointa;
        }
        else if(keyCode >= codePointZero && keyCode <= codePointNine)
        {
            return keyCode + 52 - codePointZero;
        }
        else if(keyCode == codePointPlus)
        {
            return 62;
        }
        else if(keyCode == codePointSlash)
        {
            return 63;
        }

        return undefined;
    }

    let codes = Array.from(text).map(character => getCodeFromKey(character));

    let bytesLength = Math.ceil(codes.length / 4) * 3;

    if(codes[codes.length - 2] == undefined) { bytesLength = bytesLength - 2; } else if(codes[codes.length - 1] == undefined) { bytesLength--; }

    let bytes = new Uint8Array(bytesLength);

    for(let offset = 0, index = 0; offset < bytes.length;)
    {
        let code1 = codes[index++];
        let code2 = codes[index++];
        let code3 = codes[index++];
        let code4 = codes[index++];

        let byte1 = (code1 << 2) | (code2 >> 4);
        let byte2 = ((code2 & 0xf) << 4) | (code3 >> 2);
        let byte3 = ((code3 & 0x3) << 6) | code4;

        bytes[offset++] = byte1;
        bytes[offset++] = byte2;
        bytes[offset++] = byte3;
    }

    if(binary) { return bytes; }

    return utf8ToString(bytes, true);
}

function encodeBase64(bytes) {
    if (bytes === undefined || bytes === null) {
        return '';
    }
    if (bytes instanceof Array) {
        bytes = bytes.filter(item => {
            return Number.isFinite(item) && item >= 0 && item <= 255;
        });
    }

    if (
        !(
            bytes instanceof Uint8Array ||
            bytes instanceof Uint8ClampedArray ||
            bytes instanceof Array
        )
    ) {
        if (typeof bytes === 'string') {
            const str = bytes;
            bytes = Array.from(unescape(encodeURIComponent(str))).map(ch =>
                ch.codePointAt(0)
            );
        } else {
            throw new TypeError('bytes must be of type Uint8Array or String.');
        }
    }

    const keys = [
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        '+',
        '/'
    ];
    const fillKey = '=';

    let byte1;
    let byte2;
    let byte3;
    let sign1 = ' ';
    let sign2 = ' ';
    let sign3 = ' ';
    let sign4 = ' ';

    let result = '';

    for (let index = 0; index < bytes.length; ) {
        let fillUpAt = 0;

        // tslint:disable:no-increment-decrement
        byte1 = bytes[index++];
        byte2 = bytes[index++];
        byte3 = bytes[index++];

        if (byte2 === undefined) {
            byte2 = 0;
            fillUpAt = 2;
        }

        if (byte3 === undefined) {
            byte3 = 0;
            if (!fillUpAt) {
                fillUpAt = 3;
            }
        }

        // tslint:disable:no-bitwise
        sign1 = keys[byte1 >> 2];
        sign2 = keys[((byte1 & 0x3) << 4) + (byte2 >> 4)];
        sign3 = keys[((byte2 & 0xf) << 2) + (byte3 >> 6)];
        sign4 = keys[byte3 & 0x3f];

        if (fillUpAt > 0) {
            if (fillUpAt <= 2) {
                sign3 = fillKey;
            }
            if (fillUpAt <= 3) {
                sign4 = fillKey;
            }
        }

        result += sign1 + sign2 + sign3 + sign4;

        if (fillUpAt) {
            break;
        }
    }

    return result;
}

let base64 = encodeBase64("\u{1F604}"); // unicode code point escapes for smiley
let str = decodeBase64(base64);

console.log("base64", base64);
console.log("str", str);

document.body.innerText = str;

wie man es benutzt: decodeBase64(encodeBase64("\u{1F604}"))

Demo: https://jsfiddle.net/qrLadeb8/

Martin Wantke
quelle
Funktioniert super! 🎉 Ich sehe nicht, wo Sie brauchen stringToUTF8und utf8ToStringobwohl
Benjamin Toueg
1

Ich bin gerade selbst auf dieses Problem gestoßen.

Ändern Sie zunächst Ihren Code geringfügig:

var download = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">";

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa(download);

Verwenden Sie dann Ihren bevorzugten Webinspektor, setzen Sie einen Haltepunkt in die Codezeile, die this.loader.src zuweist, und führen Sie diesen Code aus:

for (var i = 0; i < download.length; i++) {
  if (download[i].charCodeAt(0) > 255) {
    console.warn('found character ' + download[i].charCodeAt(0) + ' "' + download[i] + '" at position ' + i);
  }
}

Abhängig von Ihrer Anwendung kann das Ersetzen von Zeichen, die außerhalb des Bereichs liegen, funktionieren oder nicht, da Sie die Daten ändern. Beachten Sie den Hinweis auf MDN zu Unicode-Zeichen mit der btoa-Methode:

https://developer.mozilla.org/en-US/docs/Web/API/window.btoa

Mark Salisbury
quelle