Gagal menjalankan 'btoa' di 'Window': String yang akan dikodekan berisi karakter di luar rentang Latin1.

133

Kesalahan dalam judul hanya dibuang di Google Chrome, menurut pengujian saya. Saya base64 menyandikan file XML besar sehingga dapat diunduh:

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 tersembunyi iframe.

Kesalahan ini sebenarnya cukup berubah karena biasanya, Google Chrome akan mogok saat btoapanggilan. Mozilla Firefox tidak memiliki masalah di sini, jadi masalahnya terkait browser. Saya tidak mengetahui adanya karakter aneh dalam file. Sebenarnya saya percaya tidak ada karakter non-ascii.

T: Bagaimana cara menemukan karakter yang bermasalah dan menggantinya sehingga Chrome berhenti mengeluh?

Saya telah mencoba menggunakan Downloadify untuk memulai unduhan, tetapi tidak berhasil. Tidak dapat diandalkan dan tidak ada kesalahan untuk mengizinkan debug.

Tomáš Zato - Pasang kembali Monica
sumber

Jawaban:

212

Jika Anda memiliki UTF8, gunakan ini (sebenarnya berfungsi dengan sumber SVG), seperti:

btoa(unescape(encodeURIComponent(str)))

contoh:

 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;

Jika Anda perlu memecahkan kode base64 itu, gunakan ini:

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

Contoh:

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

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

Catatan: jika Anda perlu membuatnya bekerja di mobile-safari, Anda mungkin perlu menghapus semua ruang putih dari data base64 ...

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

Pembaruan 2017

Masalah ini telah menggangguku lagi.
Kebenaran sederhananya adalah, atob tidak benar-benar menangani string UTF8 - hanya ASCII.
Juga, saya tidak akan menggunakan bloatware seperti js-base64.
Tetapi webtoolkit memiliki implementasi yang kecil, bagus, dan sangat dapat dipertahankan:

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

  • Untuk setiap karakter yang sama dengan atau di bawah 127 (hex 0x7F), representasi UTF-8 adalah satu byte. Ini hanya 7 bit terendah dari nilai unicode penuh. Ini juga sama dengan nilai ASCII.

  • Untuk karakter yang sama atau di bawah 2047 (hex 0x07FF), representasi UTF-8 tersebar di dua byte. Byte pertama akan memiliki dua bit tinggi yang ditetapkan dan bit ketiga jelas (yaitu 0xC2 ke 0xDF). Byte kedua akan memiliki set bit atas dan bit kedua jelas (yaitu 0x80 ke 0xBF).

  • Untuk semua karakter yang sama atau lebih besar dari 2048 tetapi kurang dari 65535 (0xFFFF), representasi UTF-8 tersebar di tiga byte.

Stefan Steiger
sumber
6
bisakah kamu menjelaskan ini sedikit lebih ... aku benar-benar hilang
Muhammad Umer
Saya hanya akan menjalankan kode jika saya adalah Anda. escapekonversi string dalam satu yang hanya berisi karakter yang valid url. Itu mencegah kesalahan.
Tomáš Zato - Reinstate Monica
6
escapedan unescapeyang usang dalam JavaScript 1.5 dan satu harus menggunakan encodeURIComponentatau decodeURIComponent, masing-masing, sebagai gantinya. Anda menggunakan fungsi yang usang dan baru secara bersamaan. Mengapa? Lihat: w3schools.com/jsref/jsref_escape.asp
Leif
2
@Leif: Ini hanya berfungsi karena melarikan diri dan menghapus jejak adalah buggy (dengan cara yang sama);)
Stefan Steiger
8
Adakah orang lain yang berhenti di sini karena menggunakan webpack?
Avindra Goolcharan
18

Menggunakan btoadengan unescapedan encodeURIComponenttidak bekerja untuk saya. Mengganti semua karakter khusus dengan entitas XML / HTML dan kemudian mengonversi ke representasi base64 adalah satu-satunya cara untuk mengatasi masalah ini bagi saya. Beberapa kode:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));
Italo Borssatto
sumber
1
Karena saya memposting pertanyaan ini, saya belajar sedikit tentang API yang didedikasikan untuk apa yang saya lakukan. Jika string yang Anda konversi panjang, gunakan Blobobjek untuk menangani konversi. Blobdapat menangani data biner apa pun.
Tomáš Zato - Reinstate Monica
1
Tidak yakin tentang IE9. Tetapi saya berpikir bahwa jika Anda melakukan hal-hal seperti konversi klien-sisi base64 Anda mungkin membuat aplikasi web modern yang akan, cepat atau lambat, tetap memerlukan fitur-fitur modern. Juga, ada polyfill gumpalan.
Tomáš Zato - Reinstate Monica
1
@ItaloBorssatto Kamu seorang legenda!
codeepic
1
@ItaloBorssatto Itu satu-satunya solusi yang bekerja untuk saya. Saya membutuhkannya untuk mengambil grafik d3 svg, membuat serialisasi menggunakan XMLSerializer, menyebarkannya ke btoa () (ini adalah tempat saya menggunakan solusi Anda) untuk membuat string ASCII yang dikodekan dengan base-64, kemudian meneruskannya ke elemen gambar yang merupakan kemudian ditarik ke kanvas dan kemudian ekspor sehingga Anda dapat mengunduh gambar di ujung depan. Agak berbelit-belit dan solusi peretasan, tetapi yang tidak memerlukan grafik yang diberikan sisi server ketika pengguna ingin mengunduh beberapa gambar. Jika Anda tertarik, saya dapat mengirimkan beberapa contoh kode. Komentar itu terlalu pendek untuk mereka
codeepic
1
@ItaloBorssatto <svg xmlns = " w3.org/2000/svg " viewBox = "0 0 1060 105" width = "1060" height = "105"> <path class = "domain" stroke = "tidak ada" d = "M -6,0.5H0.5V35.5H-6 "> <line stroke =" none "x2 =" - 6 "y1 =" 0,5 "y2 =" 0,5 "fill =" tidak ada "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 = "bar pertama" fill = "rgb (25, 244, 71)" x = "0" y = "8" width = "790" height = "18" /> </ g> </svg> Saya memotong bagian yang tidak relevan. Pelakunya adalah Vogue España -> ñ mencegah gambar dimuat di browser.
codeepic
15

Gunakan perpustakaan sebagai gantinya

Kami tidak harus menemukan kembali roda. Cukup gunakan perpustakaan untuk menghemat waktu dan sakit kepala.

js-base64

https://github.com/dankogai/js-base64 bagus dan saya mengonfirmasi itu mendukung unicode dengan sangat baik.

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
sumber
Ini adalah solusi yang baik, meskipun sepertinya pengawasan untuk btoa terbatas pada ASCII (meskipun atob decoding tampaknya berfungsi dengan baik). Ini berhasil bagi saya setelah beberapa jawaban lain tidak. Terima kasih!
Untuk Nama
9

Saya hanya berpikir saya harus berbagi bagaimana saya benar-benar menyelesaikan masalah dan mengapa saya pikir ini adalah solusi yang tepat (asalkan Anda tidak mengoptimalkan browser lama).

Konversi data ke 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);

Mengizinkan pengguna menyimpan data

Terlepas dari solusi yang jelas - membuka jendela baru dengan dataURL Anda sebagai URL, Anda dapat melakukan dua hal lainnya.

1. Gunakan fileSaver.js

Penghemat file dapat membuat dialog Hemat file yang sebenarnya dengan nama file yang telah ditentukan. Itu juga dapat mundur ke pendekatan dataURL normal.

2. Gunakan (percobaan) URL.createObjectURL

Ini bagus untuk menggunakan kembali data yang disandikan base64. Itu menciptakan URL pendek untuk data AndaURL:

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

Jangan lupa untuk menggunakan URL termasuk blobawalan terkemuka . Saya menggunakan document.bodylagi:

uraian gambar; kesan

Anda dapat menggunakan URL pendek ini sebagai target AJAX, <script>sumber atau <a>lokasi href. Anda bertanggung jawab untuk menghancurkan URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
Tomáš Zato - Pasang kembali Monica
sumber
Terima kasih sobat, kamu menyelamatkan hariku :)
Sandeep Kumar
3

Sebagai pelengkap jawaban Stefan Steiger: (karena tidak terlihat bagus sebagai komentar)

Memperluas prototipe String:

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

Pemakaian:

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

CATATAN:

Sebagaimana dinyatakan dalam komentar, penggunaan unescapetidak disarankan karena dapat dihapus di masa depan:

Peringatan : Meskipun unescape () tidak sepenuhnya ditinggalkan (seperti dalam "dihapus dari standar Web"), itu didefinisikan dalam Lampiran B standar ECMA-262, yang pengantar menyatakan: ... Semua fitur bahasa dan perilaku yang ditentukan dalam ini lampiran memiliki satu atau lebih karakteristik yang tidak diinginkan dan tanpa adanya penggunaan sebelumnya akan dihapus dari spesifikasi ini.

Catatan: Jangan gunakan unescape untuk mendekode URI, gunakan decodeURI atau decodeURICon komponen .

lepe
sumber
6
Fungsinya terlihat bagus, tetapi memperluas prototipe dasar adalah praktik yang buruk.
timemachine3030
4
Javascript adalah praktik yang buruk. Apa satu hack lagi, terima kasih.
rob5408
1
@ rob5408: Meskipun saya setuju dengan pernyataan Anda pada prinsipnya, tetapi Anda harus benar-benar lebih berhati-hati: Memperluas prototipe merusak jQuery (perpustakaan lain yang menggunakan prinsip "hanya satu peretasan lagi")
Stefan Steiger
@StefanSteiger Bagus untuk tahu, terima kasih atas wawasannya.
rob5408
unescapeakan segera dihentikan sesuai MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Akansh
2

btoa () hanya mendukung karakter dari String.fromCodePoint (0) hingga String.fromCodePoint (255). Untuk karakter Base64 dengan titik kode 256 atau lebih tinggi Anda harus menyandikan / mendekodekan ini sebelum dan sesudah.

Dan pada titik ini menjadi rumit ...

Setiap tanda yang mungkin diatur dalam Tabel Unicode. Unicode-Table dibagi dalam berbagai bidang (bahasa, simbol matematika, dan sebagainya ...). Setiap tanda pada pesawat memiliki nomor titik kode yang unik. Secara teoritis, jumlahnya bisa menjadi besar secara sewenang-wenang.

Komputer menyimpan data dalam byte (8 bit, 0x00 heksadesimal - 0xff, biner 00000000 - 11111111, desimal 0 - 255). Rentang ini biasanya digunakan untuk menyimpan karakter dasar (rentang Latin1).

Untuk karakter dengan titik kode lebih tinggi maka 255 ada pengkodean yang berbeda. JavaScript menggunakan 16 bit per sign (UTF-16), string yang disebut DOMString. Unicode dapat menangani poin kode hingga 0x10fffff. Itu berarti, bahwa suatu metode harus ada untuk menyimpan beberapa bit dari beberapa sel.

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

UTF-16 menggunakan pasangan pengganti untuk menyimpan 20 bit dalam dua sel 16bit. Pengganti yang lebih tinggi pertama dimulai dengan 110110xxxxxxxxxx , yang kedua lebih rendah dengan 110111xxxxxxxxxx . Unicode memesan pesawat sendiri untuk ini: https://unicode-table.com/de/#high-surrogates

Untuk menyimpan karakter dalam byte (kisaran Latin1) prosedur standar menggunakan UTF-8 .

Maaf untuk mengatakan itu, tapi saya pikir tidak ada cara lain untuk mengimplementasikan fungsi ini sendiri.

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;

bagaimana cara menggunakannya: decodeBase64(encodeBase64("\u{1F604}"))

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

Martin Wantke
sumber
Bagus sekali! 🎉 Saya tidak melihat di mana Anda perlu stringToUTF8dan utf8ToStringmeskipun
Benjamin Toueg
1

Saya sendiri mengalami masalah ini.

Pertama, sedikit modifikasi kode Anda:

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

Kemudian gunakan inspektur web favorit Anda, letakkan breakpoint pada baris kode yang menetapkan this.loader.src, kemudian jalankan kode ini:

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

Bergantung pada aplikasi Anda, mengganti karakter yang di luar jangkauan mungkin atau mungkin tidak berfungsi, karena Anda akan memodifikasi data. Lihat catatan di MDN tentang karakter unicode dengan metode btoa:

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

Mark Salisbury
sumber