Impossible d'exécuter 'btoa' sur 'Window': la chaîne à encoder contient des caractères en dehors de la plage Latin1.

133

L'erreur dans le titre n'est lancée que dans Google Chrome, d'après mes tests. J'encode en base64 un gros fichier XML pour qu'il puisse être téléchargé:

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 est une iframe masquée.

Cette erreur est en fait tout un changement car normalement, Google Chrome plantait lors d'un btoaappel. Mozilla Firefox n'a aucun problème ici, donc le problème est lié au navigateur. Je ne connais aucun caractère étrange dans le fichier. En fait, je crois qu'il n'y a pas de caractères non-ascii.

Q: Comment trouver les caractères problématiques et les remplacer pour que Chrome cesse de se plaindre?

J'ai essayé d'utiliser Downloadify pour lancer le téléchargement, mais cela ne fonctionne pas. Il n'est pas fiable et ne génère aucune erreur pour permettre le débogage.

Tomáš Zato - Réintégrer Monica
la source

Réponses:

212

Si vous avez UTF8, utilisez ceci (fonctionne en fait avec la source SVG), comme:

btoa(unescape(encodeURIComponent(str)))

exemple:

 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;

Si vous avez besoin de décoder cette base64, utilisez ceci:

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

Exemple:

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

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

Remarque: si vous devez faire en sorte que cela fonctionne dans mobile-safari, vous devrez peut-être supprimer tout l'espace blanc des données base64 ...

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

Mise à jour 2017

Ce problème me dérange à nouveau.
La simple vérité est que atob ne gère pas vraiment les chaînes UTF8 - c'est uniquement ASCII.
De plus, je n'utiliserais pas de bloatware comme js-base64.
Mais webtoolkit a une implémentation petite, agréable et très maintenable:

/**
*
*  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

  • Pour tout caractère égal ou inférieur à 127 (hex 0x7F), la représentation UTF-8 est d'un octet. Ce ne sont que les 7 bits les plus bas de la valeur unicode complète. C'est également la même que la valeur ASCII.

  • Pour les caractères égaux ou inférieurs à 2047 (hex 0x07FF), la représentation UTF-8 est répartie sur deux octets. Le premier octet aura les deux bits hauts définis et le troisième bit clair (c'est-à-dire 0xC2 à 0xDF). Le deuxième octet aura le bit supérieur défini et le deuxième bit clair (c'est-à-dire 0x80 à 0xBF).

  • Pour tous les caractères égaux ou supérieurs à 2048 mais inférieurs à 65535 (0xFFFF), la représentation UTF-8 est répartie sur trois octets.

Stefan Steiger
la source
6
pouvez-vous expliquer un peu plus ... je suis totalement perdu
Muhammad Umer
J'exécuterais juste le code si j'étais toi. escapeconvertit la chaîne dans celle qui ne contient que des caractères valides d'URL. Cela évite les erreurs.
Tomáš Zato - Réintégrer Monica
6
escapeet unescapeétaient obsolètes dans JavaScript 1.5 et on devrait utiliser encodeURIComponentou decodeURIComponent, respectivement, à la place. Vous utilisez ensemble les fonctions obsolètes et nouvelles. Pourquoi? Voir: w3schools.com/jsref/jsref_escape.asp
Leif
2
@Leif: Cela ne fonctionne que précisément parce que escape et unescape sont bogués (de la même manière);)
Stefan Steiger
8
Quelqu'un d'autre s'est retrouvé ici après avoir utilisé Webpack?
Avindra Goolcharan
18

Utiliser btoaavec unescapeet encodeURIComponentn'a pas fonctionné pour moi. Le remplacement de tous les caractères spéciaux par des entités XML / HTML, puis la conversion vers la représentation base64 était le seul moyen de résoudre ce problème pour moi. Un peu de code:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));
Italo Borssatto
la source
1
Depuis que j'ai publié cette question, j'ai appris un peu plus sur les API dédiées à ce que je faisais. Si la chaîne que vous convertissez est longue, utilisez Blobobject pour gérer la conversion. Blobpeut gérer toutes les données binaires.
Tomáš Zato - Réintégrer Monica
1
Pas sûr de IE9. Mais je pense que si vous faites des choses comme la conversion en base64 côté client, vous créez probablement une application Web moderne qui, tôt ou tard, aura de toute façon besoin de fonctionnalités modernes. En outre, il existe un polyfill blob.
Tomáš Zato - Réintégrer Monica
1
@ItaloBorssatto Vous êtes une légende!
codeepic
1
@ItaloBorssatto C'était la seule solution qui fonctionnait pour moi. J'en avais besoin pour récupérer le graphique svg d3, le sérialiser à l'aide de XMLSerializer, le passer dans btoa () (c'est là que j'ai utilisé votre solution) pour créer une chaîne ASCII encodée en base 64, puis la passer dans l'élément image qui est puis dessiné dans le canevas, puis exportez-le afin que vous puissiez télécharger une image sur le front-end. Solution assez compliquée et piratée, mais qui ne nécessite pas de graphiques rendus côté serveur lorsque les utilisateurs souhaitent télécharger des graphiques. Si vous êtes intéressé, je peux vous envoyer des exemples de code. Le commentaire est trop court pour eux
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 "> <line stroke =" none "x2 =" - 6 "y1 =" 0.5 "y2 =" 0.5 "fill =" none "stroke-width =" 1px "font- family = "sans-serif" 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> J'ai découpé des morceaux non pertinents. Le coupable est Vogue España -> ñ empêché le chargement d'une image dans le navigateur.
codeepic
15

Utilisez plutôt une bibliothèque

Nous n'avons pas à réinventer la roue. Utilisez simplement une bibliothèque pour économiser du temps et des maux de tête.

js-base64

https://github.com/dankogai/js-base64 est bon et je confirme qu'il prend très bien en charge l'Unicode.

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
la source
C'est une bonne solution, même si cela semble être un oubli pour que le btoa soit limité à l'ASCII (bien que le décodage atob semble fonctionner correctement). Cela a fonctionné pour moi après plusieurs des autres réponses non. Merci!
Pour le nom
9

J'ai juste pensé que je devrais partager comment j'ai résolu le problème et pourquoi je pense que c'est la bonne solution (à condition que vous n'optimisez pas pour l'ancien navigateur).

Conversion de données en 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);

Permettre à l'utilisateur d'enregistrer des données

Outre la solution évidente - ouvrir une nouvelle fenêtre avec votre dataURL comme URL, vous pouvez faire deux autres choses.

1. Utilisez fileSaver.js

L'économiseur de fichiers peut créer une boîte de dialogue FileSave réelle avec un nom de fichier prédéfini. Il peut également revenir à l'approche dataURL normale.

2. Utilisation (expérimentale) URL.createObjectURL

C'est idéal pour réutiliser des données encodées en base64. Il crée une URL courte pour votre dataURL:

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

N'oubliez pas d'utiliser l'URL avec le blobpréfixe principal . J'ai utilisé à document.bodynouveau:

description de l'image

Vous pouvez utiliser cette URL courte comme cible AJAX, <script>source ou <a>emplacement href. Vous êtes cependant responsable de la destruction de l'URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
Tomáš Zato - Réintégrer Monica
la source
Merci mon pote, tu as sauvé ma journée :)
Sandeep Kumar
3

En complément de la réponse de Stefan Steiger: (car cela n'a pas l'air agréable en tant que commentaire)

Extension du prototype de chaîne:

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

Usage:

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

REMARQUE:

Comme indiqué dans les commentaires, l'utilisation unescapen'est pas recommandée car elle pourrait être supprimée à l'avenir:

Attention : Bien que unescape () ne soit pas strictement obsolète (comme dans "retiré des standards du Web"), il est défini dans l'annexe B de la norme ECMA-262, dont l'introduction indique:… Toutes les fonctionnalités et comportements de langage spécifiés dans ce annexe ont une ou plusieurs caractéristiques indésirables et en l'absence d'utilisation héritée serait supprimée de cette spécification.

Remarque: n'utilisez pas unescape pour décoder les URI, utilisez plutôt decodeURI ou decodeURIComponent .

lépe
la source
6
Les fonctions semblent bonnes, mais l'extension des prototypes de base est une mauvaise pratique.
timemachine3030
4
Javascript est une mauvaise pratique. Quoi de plus, merci.
rob5408
1
@ rob5408: Bien que je sois d'accord avec votre déclaration en principe, mais vous devriez vraiment être plus prudent: l'extension des prototypes casse jQuery (une autre bibliothèque qui utilise le principe "juste un hack de plus")
Stefan Steiger
@StefanSteiger Bon à savoir, merci pour la perspicacité.
rob5408
unescapesera bientôt obsolète selon MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Akansh
2

btoa () ne prend en charge que les caractères de String.fromCodePoint (0) à String.fromCodePoint (255). Pour les caractères Base64 avec un point de code 256 ou supérieur, vous devez les encoder / décoder avant et après.

Et sur ce point cela devient délicat ...

Chaque signe possible est organisé dans une table Unicode. La table Unicode est divisée en différents plans (langues, symboles mathématiques, etc.). Chaque signe dans un avion a un numéro de point de code unique. Théoriquement, le nombre peut devenir arbitrairement grand.

Un ordinateur stocke les données en octets (8 bits, hexadécimal 0x00 - 0xff, binaire 00000000 - 11111111, décimal 0 - 255). Cette plage est normalement utilisée pour enregistrer les caractères de base (plage Latin1).

Pour les caractères avec un point de code plus élevé, il existe 255 encodages différents. JavaScript utilise 16 bits par signe (UTF-16), la chaîne appelée DOMString. Unicode peut gérer des points de code jusqu'à 0x10fffff. Cela signifie qu'une méthode doit exister pour stocker plusieurs bits sur plusieurs cellules.

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

UTF-16 utilise des paires de substitution pour stocker 20 bits dans deux cellules 16 bits. Le premier substitut supérieur commence par 110110xxxxxxxxxx , le second inférieur par 110111xxxxxxxxxx . Unicode a réservé ses propres avions pour cela: https://unicode-table.com/de/#high-surrogates

Pour stocker des caractères en octets (plage Latin1), les procédures normalisées utilisent UTF-8 .

Désolé de le dire, mais je pense qu'il n'y a pas d'autre moyen d'implémenter cette fonction soi-même.

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;

comment l'utiliser: decodeBase64(encodeBase64("\u{1F604}"))

démo: https://jsfiddle.net/qrLadeb8/

Martin Wantke
la source
Fonctionne très bien! 🎉 Je ne vois pas où vous avez besoin stringToUTF8et utf8ToStringcependant
Benjamin Toueg
1

Je viens de rencontrer ce problème moi-même.

Tout d'abord, modifiez légèrement votre code:

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

Ensuite, utilisez votre inspecteur Web préféré, mettez un point d'arrêt sur la ligne de code qui affecte this.loader.src, puis exécutez ce code:

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

En fonction de votre application, le remplacement des caractères hors plage peut ou non fonctionner, car vous allez modifier les données. Voir la note sur MDN sur les caractères unicode avec la méthode btoa:

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

Mark Salisbury
la source