Использование aavb Javascript для декодирования base64 не правильно декодирует строки utf-8
Я использую Javascript window.atob()
функция для декодирования строки в кодировке base64 (в частности, содержимого в кодировке base64 из GitHub API). Проблема в том, что я возвращаю символы в кодировке ASCII (например, â¢
вместо ™
). Как правильно обработать входящий поток в кодировке base64, чтобы он декодировался как utf-8?
15 ответов
Есть замечательная статья о MDN-документах Mozilla, которая описывает именно эту проблему:
"Проблема Юникода" с
DOMString
s - это 16-битные строки, в большинстве браузеров вызывающиеwindow.btoa
на строке Unicode вызоветCharacter Out Of Range exception
если символ превышает диапазон 8-битного байта (0x00~0xFF). Есть два возможных способа решения этой проблемы:
- первый - экранировать всю строку (с UTF-8 см.
encodeURIComponent
) а затем закодировать его;- второй конвертировать UTF-16
DOMString
в массив символов UTF-8, а затем закодировать его.
Примечание о предыдущих решениях: в статье MDN изначально предлагалось использовать unescape
а также escape
решить Character Out Of Range
проблема исключения, но с тех пор они устарели. Некоторые другие ответы здесь предложили обойти это с decodeURIComponent
а также encodeURIComponent
Это оказалось ненадежным и непредсказуемым. В последнем обновлении этого ответа используются современные функции JavaScript для повышения скорости и модернизации кода.
Если вы пытаетесь сэкономить время, вы также можете использовать библиотеку:
Кодировка UTF8 ⇢ base64
function b64EncodeUnicode(str) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n'); // "Cg=="
Декодирование base64 ⇢ UTF8
function b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
b64DecodeUnicode('Cg=='); // "\n"
Решение до 2018 года (функционально и хотя, вероятно, лучше поддерживает старые браузеры, но не обновлено)
Вот текущая рекомендация, прямо из MDN, с некоторой дополнительной совместимостью с TypeScript через @MA-Maddin:
// Encoding UTF8 ⇢ base64
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode(parseInt(p1, 16))
}))
}
b64EncodeUnicode('✓ à la mode') // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n') // "Cg=="
// Decoding base64 ⇢ UTF8
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
}
b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=') // "✓ à la mode"
b64DecodeUnicode('Cg==') // "\n"
Исходное решение (устарело)
Это б escape
а также unescape
(которые сейчас устарели, хотя это все еще работает во всех современных браузерах):
function utf8_to_b64( str ) {
return window.btoa(unescape(encodeURIComponent( str )));
}
function b64_to_utf8( str ) {
return decodeURIComponent(escape(window.atob( str )));
}
// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
И последнее: я впервые столкнулся с этой проблемой при вызове GitHub API. Чтобы заставить это работать на (Mobile) Safari должным образом, мне фактически пришлось убрать все пустое пространство из источника base64, прежде чем я смог даже декодировать источник. Будет ли это актуально в 2017 году, я не знаю:
function b64_to_utf8( str ) {
str = str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob( str )));
}
Декодирование base64 в строку UTF8
Ниже приведен текущий ответ, получивший наибольшее количество голосов от @brandonscript
function b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
Приведенный выше код может работать, но он очень медленный. Если ваш ввод представляет собой очень большую строку base64, например 30000 символов для HTML-документа base64. Это потребует много вычислений.
Вот мой ответ: используйте встроенный TextDecoder, почти в 10 раз быстрее, чем код выше, для большого ввода.
function decodeBase64(base64) {
const text = atob(base64);
const length = text.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = text.charCodeAt(i);
}
const decoder = new TextDecoder(); // default is utf-8
return decoder.decode(bytes);
}
Вещи меняются. Методы escape / unescape устарели.
Вы можете кодировать строку URI перед тем, как кодировать ее Base64. Обратите внимание, что это не производит кодирование Base64 UTF8, а скорее данные в кодировке Base64 URL-кодированные. Обе стороны должны согласовать одну и ту же кодировку.
Смотрите рабочий пример здесь: http://codepen.io/anon/pen/PZgbPW
// encode string
var base64 = window.btoa(encodeURIComponent('€ 你好 æøåÆØÅ'));
// decode string
var str = decodeURIComponent(window.atob(tmp));
// str is now === '€ 你好 æøåÆØÅ'
Для решения проблемы OP сторонняя библиотека, такая как js-base64, должна решить проблему.
Полная статья, которая мне подходит: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
Часть, в которой мы кодируем из Unicode/UTF-8, это
function utf8_to_b64( str ) {
return window.btoa(unescape(encodeURIComponent( str )));
}
function b64_to_utf8( str ) {
return decodeURIComponent(escape(window.atob( str )));
}
// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
В настоящее время это один из наиболее часто используемых методов.
Если вам больше нравится обрабатывать строки как байты, вы можете использовать следующие функции
function u_atob(ascii) {
return Uint8Array.from(atob(ascii), c => c.charCodeAt(0));
}
function u_btoa(buffer) {
var binary = [];
var bytes = new Uint8Array(buffer);
for (var i = 0, il = bytes.byteLength; i < il; i++) {
binary.push(String.fromCharCode(bytes[i]));
}
return btoa(binary.join(''));
}
// example, it works also with astral plane characters such as ''
var encodedString = new TextEncoder().encode('✓');
var base64String = u_btoa(encodedString);
console.log('✓' === new TextDecoder().decode(u_atob(base64String)))
Я хотел бы предположить, что можно было бы хотеть, чтобы это base64 было закодировано более стандартным способом, чем некоторые решения, перечисленные выше. Этот "стандартный" способ естественно интерпретируется браузером, так что вы можете использовать base64 в URI данных. Пожалуйста, посетите этот URI данных здесь, чтобы увидеть демонстрацию: data:text/plain;base64,4pi44pi54pi64pi74pi84pi+4pi/
(Скопируйте данные URI, откройте новую вкладку, вставьте URI данных в адресную строку, затем нажмите Enter, чтобы перейти на страницу). Как вы можете видеть, несмотря на тот факт, что это URL в кодировке base64, браузер по-прежнему может распознавать старшие кодовые точки и правильно их декодировать. Таким образом, эта демонстрация доказывает, что это лучший путь, потому что это самый стандарт W3C. Ниже приведен код, используемый для его генерации.
var fromCharCode = String.fromCharCode;
var btoaUTF8 = (function(btoa, replacer){"use strict";
return function(inputString, BOMit){
return btoa((BOMit ? "\xEF\xBB\xBF" : "") + inputString.replace(
/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
));
}
})(btoa, function(nonAsciiChars){"use strict";
// make the UTF string into a binary UTF-8 encoded string
var point = nonAsciiChars.charCodeAt(0);
if (point >= 0xD800 && point <= 0xDBFF) {
var nextcode = nonAsciiChars.charCodeAt(1);
if (nextcode !== nextcode) // NaN because string is 1 code point long
return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
if (point > 0xffff)
return fromCharCode(
(0x1e/*0b11110*/<<3) | (point>>>18),
(0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
);
} else return fromCharCode(0xef, 0xbf, 0xbd);
}
if (point <= 0x007f) return inputString;
else if (point <= 0x07ff) {
return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f));
} else return fromCharCode(
(0xe/*0b1110*/<<4) | (point>>>12),
(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
);
});
Затем для декодирования данных base64 либо HTTP получает данные в виде URI данных, либо использует функцию ниже.
var clz32 = Math.clz32 || (function(log, LN2){"use strict";
return function(x) {return 31 - log(x >>> 0) / LN2 | 0};
})(Math.log, Math.LN2);
var fromCharCode = String.fromCharCode;
var atobUTF8 = (function(atob, replacer){"use strict";
return function(inputString, keepBOM){
if (!keepBOM && inputString.substring(0,3) === "\xEF\xBB\xBF")
inputString = inputString.substring(3); // eradicate UTF-8 BOM
// 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx
// 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx
return atob(inputString).replace(/[\xc0-\xff][\x80-\xbf]*/g, replacer);
}
})(atob, function(encoded){"use strict";
var codePoint = encoded.charCodeAt(0) << 24;
var leadingOnes = clz32(~codePoint);
var endPos = 0, stringLen = encoded.length;
var result = "";
if (leadingOnes < 5 && stringLen >= leadingOnes) {
codePoint = (codePoint<<leadingOnes)>>>(24+leadingOnes);
for (endPos = 1; endPos < leadingOnes; ++endPos)
codePoint = (codePoint<<6) | (encoded.charCodeAt(endPos)&0x3f/*0b00111111*/);
if (codePoint <= 0xFFFF) { // BMP code point
result += fromCharCode(codePoint);
} else if (codePoint <= 0x10FFFF) {
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
codePoint -= 0x10000;
result += fromCharCode(
(codePoint >> 10) + 0xD800, // highSurrogate
(codePoint & 0x3ff) + 0xDC00 // lowSurrogate
);
} else endPos = 0; // to fill it in with INVALIDs
}
for (; endPos < stringLen; ++endPos) result += "\ufffd"; // replacement character
return result;
});
Преимущество более стандартного состоит в том, что этот кодер и этот декодер более широко применяются, поскольку они могут использоваться в качестве действительного URL-адреса, который отображается правильно. Обратите внимание.
(function(window){
"use strict";
var sourceEle = document.getElementById("source");
var urlBarEle = document.getElementById("urlBar");
var mainFrameEle = document.getElementById("mainframe");
var gotoButton = document.getElementById("gotoButton");
var parseInt = window.parseInt;
var fromCodePoint = String.fromCodePoint;
var parse = JSON.parse;
function unescape(str){
return str.replace(/\\u[\da-f]{0,4}|\\x[\da-f]{0,2}|\\u{[^}]*}|\\[bfnrtv"'\\]|\\0[0-7]{1,3}|\\\d{1,3}/g, function(match){
try{
if (match.startsWith("\\u{"))
return fromCodePoint(parseInt(match.slice(2,-1),16));
if (match.startsWith("\\u") || match.startsWith("\\x"))
return fromCodePoint(parseInt(match.substring(2),16));
if (match.startsWith("\\0") && match.length > 2)
return fromCodePoint(parseInt(match.substring(2),8));
if (/^\\\d/.test(match)) return fromCodePoint(+match.slice(1));
}catch(e){return "\ufffd".repeat(match.length)}
return parse('"' + match + '"');
});
}
function whenChange(){
try{ urlBarEle.value = "data:text/plain;base64," + btoaUTF8(unescape(sourceEle.value), true);
} finally{ gotoURL(); }
}
sourceEle.addEventListener("change",whenChange,{passive:1});
sourceEle.addEventListener("input",whenChange,{passive:1});
// IFrame Setup:
function gotoURL(){mainFrameEle.src = urlBarEle.value}
gotoButton.addEventListener("click", gotoURL, {passive: 1});
function urlChanged(){urlBarEle.value = mainFrameEle.src}
mainFrameEle.addEventListener("load", urlChanged, {passive: 1});
urlBarEle.addEventListener("keypress", function(evt){
if (evt.key === "enter") evt.preventDefault(), urlChanged();
}, {passive: 1});
var fromCharCode = String.fromCharCode;
var btoaUTF8 = (function(btoa, replacer){
"use strict";
return function(inputString, BOMit){
return btoa((BOMit?"\xEF\xBB\xBF":"") + inputString.replace(
/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
));
}
})(btoa, function(nonAsciiChars){
"use strict";
// make the UTF string into a binary UTF-8 encoded string
var point = nonAsciiChars.charCodeAt(0);
if (point >= 0xD800 && point <= 0xDBFF) {
var nextcode = nonAsciiChars.charCodeAt(1);
if (nextcode !== nextcode) { // NaN because string is 1code point long
return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
}
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
if (point > 0xffff) {
return fromCharCode(
(0x1e/*0b11110*/<<3) | (point>>>18),
(0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
);
}
} else {
return fromCharCode(0xef, 0xbf, 0xbd);
}
}
if (point <= 0x007f) { return inputString; }
else if (point <= 0x07ff) {
return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f/*00111111*/));
} else {
return fromCharCode(
(0xe/*0b1110*/<<4) | (point>>>12),
(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
);
}
});
setTimeout(whenChange, 0);
})(window);
img:active{opacity:0.8}
<center>
<textarea id="source" style="width:66.7vw">Hello \u1234 W\186\0256ld!
Enter text into the top box. Then the URL will update automatically.
</textarea><br />
<div style="width:66.7vw;display:inline-block;height:calc(25vw + 1em + 6px);border:2px solid;text-align:left;line-height:1em">
<input id="urlBar" style="width:calc(100% - 1em - 13px)" /><img id="gotoButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAeCAMAAADqx5XUAAAAclBMVEX///9NczZ8e32ko6fDxsU/fBoSQgdFtwA5pAHVxt+7vLzq5ex23y4SXABLiiTm0+/c2N6DhoQ6WSxSyweVlZVvdG/Uz9aF5kYlbwElkwAggACxs7Jl3hX07/cQbQCar5SU9lRntEWGum+C9zIDHwCGnH5IvZAOAAABmUlEQVQoz7WS25acIBBFkRLkIgKKtOCttbv//xdDmTGZzHv2S63ltuBQQP4rdRiRUP8UK4wh6nVddQwj/NtDQTvac8577zTQb72zj65/876qqt7wykU6/1U6vFEgjE1mt/5LRqrpu7oVsn0sjZejMfxR3W/yLikqAFcUx93YxLmZGOtElmEu6Ufd9xV3ZDTGcEvGLbMk0mHHlUSvS5svCwS+hVL8loQQyfpI1Ay8RF/xlNxcsTchGjGDIuBG3Ik7TMyNxn8m0TSnBAK6Z8UZfp3IbAonmJvmsEACum6aNv7B0CnvpezDcNhw9XWsuAr7qnRg6dABmeM4dTgn/DZdXWs3LMspZ1KDMt1kcPJ6S1icWNp2qaEmjq6myx7jbQK3VKItLJaW5FR+cuYlRhYNKzGa9vF4vM5roLW3OSVjkmiGJrPhUq301/16pVKZRGFYWjTP50spTxBN5Z4EKnSonruk+n4tUokv1aJSEl/MLZU90S3L6/U6o0J142iQVp3HcZxKSo8LfkNRCtJaKYFSRX7iaoAAUDty8wvWYR6HJEepdwAAAABJRU5ErkJggg==" style="width:calc(1em + 4px);line-height:1em;vertical-align:-40%;cursor:pointer" />
<iframe id="mainframe" style="width:66.7vw;height:25vw" frameBorder="0"></iframe>
</div>
</center>
Помимо того, что приведенные выше фрагменты кода очень стандартизированы, они также очень быстры. Вместо непрямой цепочки правопреемства, когда данные должны быть преобразованы несколько раз между различными формами (например, в ответе Риккардо Галли), приведенный выше фрагмент кода является настолько прямым, насколько это возможно с точки зрения производительности. Он использует только один простой пост String.prototype.replace
вызов для обработки данных при кодировании и только один для декодирования данных при декодировании. Еще один плюс в том, что (особенно для больших струн), String.prototype.replace
позволяет браузеру автоматически обрабатывать основное управление памятью путем изменения размера строки, что приводит к значительному увеличению производительности, особенно в вечнозеленых браузерах, таких как Chrome и Firefox, которые сильно оптимизируют String.prototype.replace
, Наконец, изюминка в том, что для вас латинский скрипт исключает пользователей, строки, которые не содержат кодовых точек выше 0x7f, обрабатываются очень быстро, потому что строка остается неизменной по алгоритму замены.
Я создал хранилище github для этого решения по адресу https://github.com/anonyco/BestBase64EncoderDecoder/
Вот обновленное решение 2018 года, описанное в Ресурсах Mozilla Development.
КОДИРОВАТЬ ОТ ЮНИКОДА К B64
function b64EncodeUnicode(str) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n'); // "Cg=="
ДЕКОДИТЬ С B64 НА ЮНИКОД
function b64DecodeUnicode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
b64DecodeUnicode('Cg=='); // "\n"
Если вы пытаетесь декодировать представление Base64 данных в кодировке utf8 в узле, вы можете использовать встроенный помощник Buffer .
Buffer.from("4pyTIMOgIGxhIG1vZGU=", "base64").toString(); // '✓ à la mode'
The toString
метод Buffer по умолчанию использует utf8, но вы можете указать любую желаемую кодировку. Например, обратная операция будет выглядеть так
Buffer.from('✓ à la mode', "utf8").toString("base64"); // "4pyTIMOgIGxhIG1vZGU="
Это мое однострочное решение, сочетающее ответ Джеки Ханса и некоторый код из другого вопроса :
const utf8_encoded_text = new TextDecoder().decode(Uint8Array.from(window.atob(base_64_decoded_text).split("").map(x => x.charCodeAt(0))));
Вот код на будущее для браузеров, которые могут не иметь escape/unescape()
, Обратите внимание, что IE 9 и старше не поддерживают atob/btoa()
, так что вам нужно использовать пользовательские функции base64 для них.
// Polyfill for escape/unescape
if( !window.unescape ){
window.unescape = function( s ){
return s.replace( /%([0-9A-F]{2})/g, function( m, p ) {
return String.fromCharCode( '0x' + p );
} );
};
}
if( !window.escape ){
window.escape = function( s ){
var chr, hex, i = 0, l = s.length, out = '';
for( ; i < l; i ++ ){
chr = s.charAt( i );
if( chr.search( /[A-Za-z0-9\@\*\_\+\-\.\/]/ ) > -1 ){
out += chr; continue; }
hex = s.charCodeAt( i ).toString( 16 );
out += '%' + ( hex.length % 2 != 0 ? '0' : '' ) + hex;
}
return out;
};
}
// Base64 encoding of UTF-8 strings
var utf8ToB64 = function( s ){
return btoa( unescape( encodeURIComponent( s ) ) );
};
var b64ToUtf8 = function( s ){
return decodeURIComponent( escape( atob( s ) ) );
};
Более полный пример кодирования и декодирования UTF-8 можно найти здесь: http://jsfiddle.net/47zwb41o/
Небольшая коррекция, unescape и escape не рекомендуются, поэтому:
function utf8_to_b64( str ) {
return window.btoa(decodeURIComponent(encodeURIComponent(str)));
}
function b64_to_utf8( str ) {
return decodeURIComponent(encodeURIComponent(window.atob(str)));
}
function b64_to_utf8( str ) {
str = str.replace(/\s/g, '');
return decodeURIComponent(encodeURIComponent(window.atob(str)));
}
2023: В браузерах по-прежнему нет встроенной поддержки кодирования и декодирования base64 в UTF8.
Если вы действительно не хотите изобретать велосипед и тестировать крайние случаи, как для браузеров, так и для Node используйте https://github.com/dankogai/js-base64.
Концепция двоичной строки
Проблема с функциями заключается в том, что они обе работают на
Строки Юникода в Javascript, напротив, хранятся как серия кодовых единиц UTF-16, где каждая кодовая единица имеет значение от 0 до 65535. Передача строки Unicode в
Применяя это к конкретному примеру, представленному в вопросе, рассмотрим представления UTF-8 и UTF-16 символа Unicode «Знак торговой марки», ™. Представление этого символа в формате UTF-8 — . Представление этой последовательности в Base64 — . Кормление
Кодирование и декодирование собственных строк
Двоичное кодирование
Прежде чем мы сможем преобразовать строку Unicode в Base64, нам нужно выбрать двоичную кодировку для этой строки. Это может быть UTF-8, UTF-16 или любая другая кодировка, способная представлять исходную строку. Мы можем написать несколько функций для преобразования собственных строк в двоичные строки для определенных кодировок:
Собственная строка для UTF-8
function encodeAsUTF8(str) {
const encoder = new TextEncoder();
const utf8 = encoder.encode(str);
var binaryString = '';
for (let b = 0; b < utf8.length; ++b) {
binaryString += String.fromCharCode(utf8[b]);
}
return binaryString;
}
Собственная строка для UTF-16
function encodeAsUTF16(str) {
var utf16 = new Uint16Array(str.length);
for (let p = 0; p < utf16.length; ++p) {
utf16[p] = str.charCodeAt(p);
}
const bytes = new Uint8Array(utf16.buffer);
var binaryString = '';
for (let b = 0; b < bytes.length; ++b) {
binaryString += String.fromCharCode(bytes[b]);
}
return binaryString;
}
Возможны и другие кодировки, но двух приведенных выше должно быть достаточно для иллюстрации концепции.
Декодирование
Преобразование из двоичной кодировки в собственную строку требует знания исходной кодировки, чтобы двоичные значения правильно интерпретировались. Снова взяв в качестве примера UTF-8 и UTF-16, мы можем написать функции для преобразования двоичных строк UTF-8 и UTF-16 в собственные строки:
UTF-8 в собственную строку
function decodeUTF8(binary) {
const bytes = new Uint8Array(binary.length);
for (let b = 0; b < bytes.length; ++b) {
bytes[b] = binary.charCodeAt(b);
}
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
UTF-16 в собственную строку
function decodeUTF16(binary) {
const utf16 = new Uint8Array(binary.length);
for (let b = 0; b < utf16.length; ++b) {
utf16[b] = binary.charCodeAt(b);
}
const decoder = new TextDecoder('utf-16');
return decoder.decode(utf16);
}
Собственная строка в Base64
Имея различные функции кодирования строк, мы можем закодировать строку как UTF-8 и, в свою очередь, преобразовать ее в Base64, вызвав:
base64string = btoa(encodeAsUTF8('™'));
Мы также можем закодировать строку как UTF-16 и преобразовать ее в Base64, вызвав:
base64string = btoa(encodeAsUTF16('™'));
Base64 в собственную строку
Чтобы преобразовать строку в кодировке UTF-8 из Base64 в собственную строку, вызовите:
decodeUTF8(atob(base64string));
Чтобы преобразовать строку в кодировке UTF-16 из Base64 в собственную строку, вызовите:
decodeUTF16(atob(base64string));
Если вы все еще сталкиваетесь с проблемой, включите вышеуказанное решение, попробуйте, как показано ниже, рассмотрите случай, когда escape не поддерживается для TS
blob = new Blob(["\ufeff", csv_content]); // this will make symbols to appears in excel
для csv_content вы можете попробовать как ниже.
function b64DecodeUnicode(str: any) {
return decodeURIComponent(atob(str).split('').map((c: any) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}