Nie udało się wykonać „btoa” w „oknie”: ciąg do zakodowania zawiera znaki spoza zakresu Latin1.


133

Według moich testów błąd w tytule jest wyrzucany tylko w Google Chrome. Koduję base64 duży plik XML, aby można go było pobrać:

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 jest ukryty iframe.

Ten błąd jest w rzeczywistości sporą zmianą, ponieważ normalnie Google Chrome ulega awarii po btoawywołaniu. Mozilla Firefox nie ma tutaj problemów, więc problem dotyczy przeglądarki. Nie znam żadnych dziwnych znaków w pliku. Właściwie wierzę, że nie ma znaków spoza ASCII.

P: Jak znaleźć problematyczne postacie i wymienić je, aby Chrome przestał narzekać?

Próbowałem użyć Downloadify, aby zainicjować pobieranie, ale to nie działa. Jest zawodny i nie generuje żadnych błędów, aby umożliwić debugowanie.

Odpowiedzi:


212

Jeśli masz UTF8, użyj tego (faktycznie działa ze źródłem SVG), na przykład:

btoa(unescape(encodeURIComponent(str)))

przykład:

 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;

Jeśli chcesz zdekodować ten base64, użyj tego:

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

Przykład:

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

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

Uwaga: jeśli chcesz, aby to działało w mobilnym safari, może być konieczne usunięcie całego odstępu z danych base64 ...

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

Aktualizacja 2017

Ten problem znowu mnie niepokoi.
Prosta prawda jest taka, że ​​atob tak naprawdę nie obsługuje łańcuchów UTF8 - to tylko ASCII.
Nie użyłbym też oprogramowania typu bloatware, takiego jak js-base64.
Ale webtoolkit ma małą, ładną i łatwą w utrzymaniu implementację:

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

  • Dla dowolnego znaku równego lub mniejszego niż 127 (szesnastkowo 0x7F) reprezentacja UTF-8 to jeden bajt. To tylko najniższe 7 bitów pełnej wartości Unicode. Jest to również to samo, co wartość ASCII.

  • W przypadku znaków równych 2047 lub mniejszych (szesnastkowo 0x07FF) reprezentacja UTF-8 jest rozłożona na dwa bajty. Pierwszy bajt będzie miał ustawione dwa wysokie bity, a trzeci bit wyczyszczony (tj. 0xC2 do 0xDF). Drugi bajt będzie miał ustawiony górny bit, a drugi bit wyczyszczony (tj. 0x80 do 0xBF).

  • Dla wszystkich znaków równych lub większych niż 2048, ale mniejszych niż 65535 (0xFFFF), reprezentacja UTF-8 jest rozłożona na trzy bajty.


6
czy możesz to trochę wyjaśnić ... jestem całkowicie zagubiony
Muhammad Umer

Po prostu uruchomiłbym kod na twoim miejscu. escapekonwertuje ciąg w taki, który zawiera tylko prawidłowe znaki adresu URL. To zapobiega błędom.
Tomáš Zato - Przywróć Monikę

6
escapei unescapezostały uznane za przestarzałe w JavaScript 1.5 i zamiast tego należy użyć odpowiednio encodeURIComponentlub decodeURIComponent. Używasz jednocześnie przestarzałych i nowych funkcji. Czemu? Zobacz: w3schools.com/jsref/jsref_escape.asp
Leif

2
@Leif: To działa tylko dlatego, że ucieczka i unescape są buggy (w ten sam sposób);)
Stefan Steiger

8
Czy ktoś jeszcze skończył tutaj, używając webpacka?
Avindra Goolcharan

18

Korzystanie btoaz unescapei encodeURIComponentnie działa dla mnie. Zastąpienie wszystkich znaków specjalnych encjami XML / HTML, a następnie konwersja do reprezentacji base64 było jedynym sposobem rozwiązania tego problemu. Jakiś kod:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));

1
Odkąd opublikowałem to pytanie, dowiedziałem się trochę o interfejsach API przeznaczonych do tego, co robię. Jeśli konwertowany ciąg jest długi, użyj Blobobiektu do obsługi konwersji. Blobmoże obsługiwać dowolne dane binarne.
Tomáš Zato - Przywróć Monikę

1
Nie jestem pewien co do IE9. Ale myślę, że jeśli robisz rzeczy takie jak po stronie klienta konwersji base64, prawdopodobnie tworzysz nowoczesną aplikację internetową, która prędzej czy później i tak będzie potrzebować nowoczesnych funkcji. Istnieje również wypełnienie typu blob.
Tomáš Zato - Przywróć Monikę

1
@ItaloBorssatto Jesteś legendą!
codeepic

1
@ItaloBorssatto To było jedyne rozwiązanie, które u mnie zadziałało. Potrzebowałem go, aby pobrać wykres svg d3, serializować go za pomocą XMLSerializer, przekazać go do btoa () (tutaj użyłem twojego rozwiązania), aby utworzyć ciąg ASCII zakodowany w base-64, a następnie przekazać go do elementu obrazu, który jest następnie narysowany na płótnie, a następnie wyeksportuj go, aby móc pobrać obraz na interfejs użytkownika. Raczej zawiłe i hakerskie rozwiązanie, ale takie, które nie wymaga wykresów renderowanych po stronie serwera, gdy użytkownicy chcą pobrać trochę grafiki. Jeśli jesteś zainteresowany, mogę wysłać próbki kodu. Komentarz jest dla nich za krótki
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> Wycinam nieistotne fragmenty. Winowajcą jest Vogue España -> ñ zapobiega ładowaniu obrazu w przeglądarce.
codeepic

15

Zamiast tego użyj biblioteki

Nie musimy odkrywać koła na nowo. Wystarczy skorzystać z biblioteki, aby zaoszczędzić czas i ból głowy.

js-base64

https://github.com/dankogai/js-base64 jest dobry i potwierdzam, że bardzo dobrze obsługuje 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-');  // 小飼弾

Jest to dobre rozwiązanie, chociaż wydaje się, że przeoczenie btoa ogranicza się do ASCII (chociaż dekodowanie atob wydaje się działać dobrze). To zadziałało dla mnie, mimo że kilka innych odpowiedzi nie zadziałało. Dzięki!
Imię

9

Pomyślałem, że powinienem podzielić się tym, jak faktycznie rozwiązałem problem i dlaczego uważam, że jest to właściwe rozwiązanie (pod warunkiem, że nie zoptymalizujesz pod starą przeglądarkę).

Konwertowanie danych na 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);

Umożliwienie użytkownikowi zapisywania danych

Oprócz oczywistego rozwiązania - otwierając nowe okno z Twoim dataURL jako URL możesz zrobić jeszcze dwie rzeczy.

1. Użyj fileSaver.js

Funkcja oszczędzania plików może tworzyć rzeczywiste okno dialogowe Zapisz plik ze wstępnie zdefiniowaną nazwą pliku. Może również powrócić do normalnego podejścia dataURL.

2. Użyj (eksperymentalnie) URL.createObjectURL

Jest to świetne rozwiązanie do ponownego wykorzystywania danych zakodowanych w standardzie base64. Tworzy krótki adres URL dla Twojego dataURL:

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

Nie zapomnij użyć adresu URL zawierającego początkowy blobprefiks. Użyłem document.bodyponownie:

opis obrazu

Możesz użyć tego krótkiego adresu URL jako lokalizacji docelowej, <script>źródłowej lub <a>href AJAX . Jesteś jednak odpowiedzialny za zniszczenie adresu URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')

Dzięki kolego, uratowałeś mi dzień :)
Sandeep Kumar

3

Jako uzupełnienie odpowiedzi Stefana Steigera: (bo nie wygląda to ładnie jako komentarz)

Rozszerzający prototyp String:

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

Stosowanie:

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

UWAGA:

Jak stwierdzono w komentarzach, używanie unescapenie jest zalecane, ponieważ może zostać usunięte w przyszłości:

Ostrzeżenie : chociaż unescape () nie jest ściśle przestarzałe (jak w przypadku „usunięto ze standardów internetowych”), jest zdefiniowane w załączniku B do standardu ECMA-262, którego wprowadzenie brzmi:… Wszystkie funkcje językowe i zachowania określone w tym załącznik ma jedną lub więcej niepożądanych cech i w przypadku braku starszego użycia zostanie usunięty z niniejszej specyfikacji.

Uwaga: nie używaj unescape do dekodowania identyfikatorów URI, używaj decodeURI lub decodeURIComponent zamiast tego .


6
Funkcje wyglądają dobrze, ale rozszerzanie podstawowych prototypów jest złą praktyką.
timemachine 3030

4
Javascript to zła praktyka. Co więcej, dzięki.
rob5408

1
@ rob5408: Chociaż zasadniczo zgadzam się z twoim stwierdzeniem, ale powinieneś być bardziej ostrożny: rozszerzanie prototypów psuje jQuery (kolejną bibliotekę, która używa zasady „jeszcze jeden włamanie”)
Stefan Steiger

@StefanSteiger Dobrze wiedzieć, dzięki za wgląd.
rob5408

unescapewkrótce zostanie wycofany zgodnie z MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
Akansh

2

btoa () obsługuje tylko znaki od String.fromCodePoint (0) do String.fromCodePoint (255). W przypadku znaków Base64 z punktem kodowym 256 lub wyższym należy je zakodować / zdekodować przed i po.

I w tym miejscu staje się trudne ...

Każdy możliwy znak jest umieszczony w tabeli Unicode. Tablica Unicode jest podzielona na różne płaszczyzny (języki, symbole matematyczne i tak dalej ...). Każdy znak w samolocie ma unikalny numer punktu kodowego. Teoretycznie liczba może stać się dowolnie duża.

Komputer przechowuje dane w bajtach (8 bitów, szesnastkowo 0x00 - 0xff, dwójkowo 00000000 - 11111111, dziesiętnie 0 - 255). Ten zakres zwykle służy do zapisywania podstawowych znaków (zakres Latin1).

Dla znaków o wyższym punkcie kodowym niż 255 istnieją różne kodowania. JavaScript używa 16 bitów na znak (UTF-16), ciąg o nazwie DOMString. Unicode może obsługiwać punkty kodowe do 0x10fffff. Oznacza to, że musi istnieć metoda przechowywania kilku bitów w kilku komórkach dalej.

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

UTF-16 używa zastępczych par do przechowywania 20 bitów w dwóch 16-bitowych komórkach. Pierwszy wyższy surogat zaczyna się od 110110xxxxxxxxxx , dolny drugi od 110111xxxxxxxxxx . Unicode zarezerwował w tym celu własne samoloty: https://unicode-table.com/de/#high-surrogates

Aby przechowywać znaki w bajtach (zakres Latin1), standardowe procedury używają UTF-8 .

Przykro mi to mówić, ale myślę, że nie ma innego sposobu na zaimplementowanie tej funkcji.

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;

Jak tego użyć: decodeBase64(encodeBase64("\u{1F604}"))

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


Działa świetnie! 🎉 Nie wiem, gdzie potrzebujesz stringToUTF8i utf8ToStringchociaż
Benjamin Toueg,

1

Właśnie sam napotkałem ten problem.

Najpierw nieznacznie zmodyfikuj kod:

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

Następnie użyj swojego ulubionego inspektora sieci, umieść punkt przerwania w linii kodu, który przypisuje this.loader.src, a następnie wykonaj ten kod:

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

W zależności od aplikacji, zastępowanie znaków spoza zakresu może, ale nie musi, działać, ponieważ będziesz modyfikować dane. Zobacz uwagę na MDN o znakach Unicode w metodzie btoa:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.