Не удалось выполнить "btoa" в "Window": кодируемая строка содержит символы за пределами диапазона Latin1.
ошибка в названии выбрасывается только в Google Chrome, согласно моим тестам. Я base64 кодирует большой XML-файл, чтобы его можно было загрузить:
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
скрыт iframe.
эта ошибка на самом деле довольно изменение, потому что обычно, Google Chrome рухнет на btoa
звонок. Mozilla Firefox не имеет проблем здесь, поэтому проблема связана с браузером.
Я не знаю никаких странных символов в файле. На самом деле я считаю, что нет не-ascii письмена.
Q: Как найти проблемные символы и заменить их, чтобы Chrome перестал жаловаться?
я попытался использовать Downloadify для запуска загрузки, но это не работает. Это ненадежно и не вызывает ошибок, чтобы разрешить отладку.
7 ответов
если у вас есть UTF8, используйте это (на самом деле работает с источником SVG), например:
btoa(unescape(encodeURIComponent(str)))
пример:
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;
Если вам нужно расшифровать этот base64, используйте это:
var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);
пример:
var str = "äöüÄÖÜçéèñ";
var b64 = window.btoa(unescape(encodeURIComponent(str)))
console.log(b64);
var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);
Примечание: если вам нужно заставить это работать в mobile-safari, вам может потребоваться удалить все пробелы из данных base64...
function b64_to_utf8( str ) {
str = str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob( str )));
}
2017 обновления
эта проблема была опять меня достает.
Простая истина заключается в том, что atob действительно не обрабатывает UTF8-строки - это только ASCII.
Кроме того, я бы не использовал вирусы, такие как js-base64.
Но!--27-->webtoolkit имеет небольшую, приятную и очень ремонтопригодную реализацию:
/**
*
* 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
}
Я просто подумал, что должен поделиться тем, как я на самом деле решил проблему и почему я думаю, что это право решение (при условии, что вы не оптимизируете для старого браузера).
преобразование данных в 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);
позволяет пользователю сохранять данные
помимо очевидного решения-открытие нового окна с вашим dataURL в качестве URL вы можете сделать две другие вещи.
1. Использовать fileSaver.js
saver может создать фактический диалог сохранения файлов с предопределенным именем файла. Он также может вернуться к нормальному подходу dataURL.2. Использовать (экспериментально)URL.createObjectURL
это отлично подходит для повторного использования данных в кодировке base64. Он создает короткий URL для вашего dataURL:
console.log(URL.createObjectURL(blob));
//Prints: blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42
не забудьте использовать URL, включая ведущий blob
префикс. Я использовал document.body
еще раз:
вы можете использовать этот короткий URL в качестве цели AJAX, <script>
источник или <a>
местоположение href. Вы несете ответственность за уничтожение URL-адреса, хотя:
URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
используя btoa
С unescape
и encodeURIComponent
не работает для меня. Замена всех специальных карактеров XML / HTML-сущностями, а затем преобразование в представление base64 было единственным способом решить эту проблему для меня. Код:
base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
return '&#' + c.charCodeAt(0) + ';';
}));
вместо этого используйте библиотеку
нам не нужно изобретать колесо. Просто используйте библиотеку, чтобы сэкономить время и головную боль.
js-base64
https://github.com/dankogai/js-base64 хорошо и я подтверждаю очень хорошо поддерживает Юникод.
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-'); // 小飼弾
в качестве дополнения к ответу Штефана Штайгера: (поскольку это не выглядит хорошо как комментарий)
расширение прототипа строки:
String.prototype.b64encode = function() {
return btoa(unescape(encodeURIComponent(this)));
};
String.prototype.b64decode = function() {
return decodeURIComponent(escape(atob(this)));
};
использование:
var str = "äöüÄÖÜçéèñ";
var encoded = str.b64encode();
console.log( encoded.b64decode() );
Я сам только что столкнулся с этой проблемой.
во-первых, слегка измените свой код:
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);
затем используйте свой любимый веб-инспектор, поставьте точку останова в строке кода, которая назначает это.погрузчик.src, затем выполните этот код:
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);
}
}
в зависимости от вашего приложения замена символов, которые находятся вне диапазона, может работать или не работать, так как вы будете изменять данные. См. примечание по MDN о символах Юникода с btoa метод:
https://developer.mozilla.org/en-US/docs/Web/API/window.btoa
btoa () поддерживает только символы из строки.fromCodePoint (0) до строки.fromCodePoint (255). Для символов Base64 с кодовой точкой 256 или выше вам нужно кодировать / декодировать их до и после.
и в этом пункте это становится сложным...
все возможные знаки расположены в таблице Unicode. Unicode-таблица делится на разные плоскости (языки, математические символы и так далее...). Каждый знак на плоскости имеет уникальный кодовый номер. Теоретически число может стать сколь угодно большим.
компьютер хранит данные в байтах (8 бит, шестнадцатеричный 0x00 - 0xff, двоичный 00000000 - 11111111, десятичный 0 - 255). Этот диапазон обычно используется для сохранения основных символов (диапазон Latin1).
для символов с более высокой кодовой точкой существует 255 различных кодировок. JavaScript использует 16 бит на знак (UTF-16), строка называется DOMString. Unicode может обрабатывать кодовые точки до 0x10fffff. Это означает, что метод должен существовать для хранения несколько биты за несколько клеток от.
String.fromCodePoint(0x10000).length == 2
UTF-16 можно использовать суррогатные пары в магазине 20bits в два 16-разрядных ячеек. Первый высший суррогат начинается с 110110xxxxxxxxxx, Нижний второй с 110111xxxxxxxxxx. Unicode зарезервировал собственные плоскости для этого:https://unicode-table.com/de/#high-surrogates
для хранения символов в байтах (диапазон Latin1) используются стандартизированные процедуры UTF-8.
Извините, но я думаю, что нет другого способа реализовать эту функцию самостоятельно.
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")
{
let 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.");
}
}
let 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', '+', '/'];
let fillKey = '=';
let byte1 = undefined, byte2 = undefined, byte3 = undefined;
let sign1 = ' ', sign2 = ' ', sign3 = ' ', sign4 = ' ';
let result = "";
for(let index = 0; index < bytes.length; )
{
let fillUpAt = false;
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; }
}
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;
как использовать: decodeBase64(encodeBase64("\u{1F604}"))