Falha ao executar 'btoa' na 'Janela': a sequência a ser codificada contém caracteres fora do intervalo Latin1.

133

O erro no título é lançado apenas no Google Chrome, de acordo com meus testes. Estou codificando base64 um grande arquivo XML para que possa ser baixado:

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á oculto iframe.

Esse erro é realmente uma mudança, porque, normalmente, o Google Chrome trava após a btoachamada. O Mozilla Firefox não tem problemas aqui, por isso o problema está relacionado ao navegador. Não conheço nenhum caractere estranho no arquivo. Na verdade, acredito que não existem caracteres não-ascii.

P: Como encontro os caracteres problemáticos e os substituo para que o Chrome pare de reclamar?

Tentei usar o Downloadify para iniciar o download, mas ele não funciona. Não é confiável e não gera erros para permitir a depuração.

Tomáš Zato - Restabelecer Monica
fonte

Respostas:

212

Se você possui UTF8, use isso (realmente funciona com fonte SVG), como:

btoa(unescape(encodeURIComponent(str)))

exemplo:

 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;

Se você precisar decodificar essa base64, use o seguinte:

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

Exemplo:

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

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

Nota: se você precisar fazer isso funcionar no safari móvel, poderá ser necessário remover todo o espaço em branco dos dados base64 ...

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

Atualização de 2017

Esse problema está me incomodando novamente.
A verdade simples é que o atob realmente não lida com UTF8 - é apenas ASCII.
Além disso, eu não usaria bloatware como o js-base64.
Mas o webtoolkit possui uma implementação pequena, agradável e de fácil manutenção:

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

  • Para qualquer caractere igual ou inferior a 127 (hex 0x7F), a representação UTF-8 é de um byte. São apenas os 7 bits mais baixos do valor unicode completo. Também é o mesmo que o valor ASCII.

  • Para caracteres iguais ou inferiores a 2047 (hex 0x07FF), a representação UTF-8 está espalhada por dois bytes. O primeiro byte terá os dois bits altos definidos e o terceiro bit limpo (ou seja, 0xC2 a 0xDF). O segundo byte terá o bit superior definido e o segundo bit limpo (ou seja, 0x80 a 0xBF).

  • Para todos os caracteres iguais ou superiores a 2048, mas inferiores a 65535 (0xFFFF), a representação UTF-8 está espalhada por três bytes.

Stefan Steiger
fonte
6
você pode exlpain isso um pouco mais ... im totalmente perdido
Muhammad Umer
Eu apenas rodaria o código se fosse você. escapeconverte a string na que contém apenas caracteres válidos de URL. Isso evita os erros.
Tomáš Zato - Restabelece Monica
6
escapee unescapeforam preteridos no JavaScript 1.5 e deve-se usar encodeURIComponentou decodeURIComponent, respectivamente, em vez disso. Você está usando as funções preterida e nova juntas. Por quê? Veja: w3schools.com/jsref/jsref_escape.asp
Leif
2
@Leif: Isso só funciona precisamente porque fuga e unescape são de buggy (da mesma forma);)
Stefan Steiger
8
Alguém mais acabou aqui usando o webpack?
Avindra Goolcharan
18

Usando btoacom unescapee encodeURIComponentnão funcionou para mim. Substituir todos os caracteres especiais por entidades XML / HTML e depois converter para a representação base64 foi a única maneira de resolver esse problema para mim. Algum código:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));
Italo Borssatto
fonte
1
Desde que publiquei essa pergunta, aprendi um pouco sobre APIs dedicadas ao que estava fazendo. Se a string que você está convertendo for longa, use Blobobject para manipular a conversão. Blobpode lidar com qualquer dado binário.
Tomáš Zato - Restabelece Monica
1
Não tenho certeza sobre o IE9. Mas meu pensamento é que, se você estiver fazendo coisas como a conversão do base64 no lado do cliente, provavelmente estará criando um aplicativo da web moderno que, mais cedo ou mais tarde, precisará de recursos modernos de qualquer maneira. Além disso, há um polyfill de blob.
Tomáš Zato - Restabelecer Monica
1
@ItaloBorssatto Você é uma lenda!
codeepic
1
@ItaloBorssatto Foi a única solução que funcionou para mim. Eu precisava dele para pegar o gráfico d3 svg, serializá-lo usando XMLSerializer, passá-lo para btoa () (este é o local onde usei sua solução) para criar uma string ASCII codificada em base 64 e depois passá-la para o elemento de imagem que é depois desenhados na tela e depois exportá-los para que você possa baixar uma imagem no front end. Solução bastante complicada e hacky, mas que não exige gráficos renderizados no servidor quando os usuários desejam baixar alguns gráficos. Se você estiver interessado, posso enviar alguns exemplos de código. O comentário é curta demais para eles
codeepic
1
@ItaloBorssatto <svg xmlns = " w3.org/2000/svg " viewBox = "0 0 1060 105" width = "1060" height = "105"> <caminho classe = "domínio" golpe = "nenhum" d = "M -6,0,5H0,5V35,5H-6 "> <traçado da linha =" nenhum "x2 =" - 6 "y1 =" 0,5 "y2 =" 0,5 "preenchimento =" nenhum "largura do traçado =" 1px "font- família = "sans-serif" tamanho da fonte = "10px" /> <text fill = "rgb (196, 196, 196)" x = "- 9" y = "0,5" dy = "0,32em"> VogueEspana - Vogue España </text> <ret class = "primeira barra" fill = "rgb (25, 244, 71)" x = "0" y = "8" width = "790" height = "18" /> </ g> </svg> Cortei pedaços irrelevantes. O culpado é Vogue España -> ñ impediu que uma imagem fosse carregada no navegador.
codeepic
15

Use uma biblioteca

Não precisamos reinventar a roda. Basta usar uma biblioteca para economizar tempo e dor de cabeça.

js-base64

https://github.com/dankogai/js-base64 é bom e confirmo que ele suporta muito bem o 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
fonte
Essa é uma boa solução, embora pareça uma supervisão para o btoa ser limitado ao ASCII (embora a decodificação de atob pareça funcionar bem). Isso funcionou para mim depois que várias das outras respostas não funcionaram. Obrigado!
Para o nome
9

Apenas pensei em compartilhar como realmente resolvi o problema e por que acho que essa é a solução certa (desde que você não otimize o navegador antigo).

Convertendo dados em 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);

Permitindo que o usuário salve dados

Além da solução óbvia - abrindo uma nova janela com o dataURL como URL, você pode fazer duas outras coisas.

1. Use fileSaver.js

O economizador de arquivos pode criar uma caixa de diálogo fileSave real com o nome de arquivo predefinido. Também pode recorrer à abordagem normal de dataURL.

2. Uso (experimental) URL.createObjectURL

Isso é ótimo para reutilizar dados codificados em base64. Ele cria uma URL curta para o seu dataURL:

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

Não se esqueça de usar o URL, incluindo o blobprefixo principal . Eu usei document.bodynovamente:

Descrição da imagem

Você pode usar esse URL curto como destino AJAX, <script>origem ou <a>local href. Você é responsável por destruir o URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
Tomáš Zato - Restabelecer Monica
fonte
Graças companheiro, você salvou meu dia :)
Sandeep Kumar
3

Como complemento à resposta de Stefan Steiger: (como não parece bom como um comentário)

Protótipo de String de extensão:

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

Uso:

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

NOTA:

Conforme declarado nos comentários, o uso unescapenão é recomendado, pois pode ser removido no futuro:

Aviso : Embora o unescape () não seja estritamente reprovado (como em "removido dos padrões da Web"), ele é definido no Anexo B da norma ECMA-262, cuja introdução indica:… Todos os recursos e comportamentos de idioma especificados neste O anexo possui uma ou mais características indesejáveis ​​e, na ausência de uso legado, seria removido desta especificação.

Nota: Não use unescape para decodificar URIs, use decodeURI ou decodeURIComponent .

lepe
fonte
6
As funções parecem boas, mas estender os protótipos de base é uma prática ruim.
precisa saber é o seguinte
4
Javascript é uma má prática. O que é mais um truque, obrigado.
rob5408
1
@ rob5408: Embora concorde com a sua declaração, em princípio, mas você realmente deve ser mais cauteloso: Estendendo protótipos breakes jQuery (outra biblioteca que utiliza a "apenas mais um hack" princípio)
Stefan Steiger
@StefanSteiger É bom saber, obrigado pela compreensão.
rob5408
unescapeserá preterido em breve, de acordo com o MDN developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Akansh
2

btoa () suporta apenas caracteres de String.fromCodePoint (0) até String.fromCodePoint (255). Para caracteres Base64 com um ponto de código 256 ou superior, é necessário codificá-los / decodificá-los antes e depois.

E, neste ponto, torna-se complicado ...

Todo sinal possível é organizado em uma tabela Unicode. A tabela Unicode é dividida em diferentes planos (idiomas, símbolos matemáticos etc.). Cada sinal em um avião tem um número de ponto de código exclusivo. Teoricamente, o número pode se tornar arbitrariamente grande.

Um computador armazena os dados em bytes (8 bits, hexadecimal 0x00 - 0xff, binário 00000000 - 11111111, decimal 0 - 255). Esse intervalo normalmente é usado para salvar caracteres básicos (intervalo Latin1).

Para caracteres com maior ponto de código, existem 255 codificações diferentes. O JavaScript usa 16 bits por sinal (UTF-16), a sequência chamada DOMString. Unicode pode manipular pontos de código até 0x10fffff. Isso significa que um método deve existir para armazenar vários bits em várias células de distância.

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

O UTF-16 usa pares substitutos para armazenar 20 bits em duas células de 16 bits. O primeiro substituto superior começa com 110110xxxxxxxxxx , o segundo inferior com 110111xxxxxxxxxxxx . Unicode reservou aviões próprios para isso: https://unicode-table.com/de/#high-surrogates

Para armazenar caracteres em procedimentos padronizados de bytes (intervalo Latin1), use UTF-8 .

Desculpe dizer isso, mas acho que não há outra maneira de implementar essa função.

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;

como usá-lo: decodeBase64(encodeBase64("\u{1F604}"))

demo: https://jsfiddle.net/qrLadeb8/

Martin Wantke
fonte
Funciona bem! 🎉 Não vejo onde você precisa stringToUTF8e, no utf8ToStringentanto,
Benjamin
1

Acabei de me deparar com esse problema.

Primeiro, modifique seu código levemente:

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

Em seguida, use seu inspetor da web favorito, coloque um ponto de interrupção na linha de código que atribui this.loader.src e execute este código:

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

Dependendo do seu aplicativo, a substituição dos caracteres que estão fora do intervalo pode ou não funcionar, pois você modificará os dados. Veja a nota no MDN sobre caracteres unicode com o método btoa:

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

Mark Salisbury
fonte