API HTML5 File, читаемый как текстовый и двоичный

В настоящее время я работаю над API файлов HTML5, и мне нужно получить данные двоичных файлов. The FileReader readAsText и readAsDataURL работают нормально, но readAsBinaryString возвращает те же данные, что и readAsText.

Мне нужны двоичные данные, но я получаю текстовую строку. Я что-то пропустил?

Ответ 1

readAsBinaryString говорит, что данные должны быть представлены как двоичная строка, где:

... каждый байт представлен целым числом в диапазоне [0..255].

JavaScript изначально не имел "двоичного" типа (до тех пор, пока поддержка WebGL в ECMAScript 5 не будет Typed Array * (подробнее см. ниже) - it был заменен ECMAScript 2015 ArrayBuffer), и поэтому они пошли со строкой с гарантией того, что ни один символ, хранящийся в String, не будет находиться за пределами диапазон 0,255. (Они могли бы пойти с массивом чисел вместо этого, но они этого не сделали, возможно, большие строки более эффективны с точки зрения памяти, чем большие массивы чисел, поскольку числа являются плавающей точкой.)

Если вы читаете файл, который в основном используется в западном script (в основном, английском языке), то эта строка будет очень похожа на текст. Если вы читаете файл с символами Unicode в нем, вы должны заметить разницу, поскольку строки JavaScript UTF-16 ** (подробнее см. Ниже) и поэтому некоторые символы будут иметь значения выше 255, тогда как "двоичная строка" в соответствии со спецификацией API файлов не будет иметь никаких значений выше 255 (у вас есть два отдельных "символа" для двух байтов кодовой точки Юникода).

Если вы читаете файл, который не является текстом вообще (возможно, изображение), вы, вероятно, все равно получите очень похожий результат между readAsText и readAsBinaryString, но с readAsBinaryString вы знаете, что там не будет никакой попытки интерпретировать многобайтовые последовательности как символы. Вы не знаете, что если вы используете readAsText, потому что readAsText будет использовать определение , чтобы попытаться выяснить, что кодирует файл а затем сопоставить его с строками JavaScript UTF-16.

Вы можете увидеть эффект, если вы создадите файл и сохраните его в чем-то, отличном от ASCII или UTF-8. (В Windows это можно сделать с помощью "Блокнота", "Сохранить как" в качестве раскрывающегося списка с "Юникодом", на котором, глядя на данные, они, похоже, означают UTF-16, я уверен, что Mac OS и * nix имеют аналогичную функцию.) Здесь страница, которая сбрасывает результат чтения файла в обоих направлениях:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Show File Data</title>
<style type='text/css'>
body {
    font-family: sans-serif;
}
</style>
<script type='text/javascript'>

    function loadFile() {
        var input, file, fr;

        if (typeof window.FileReader !== 'function') {
            bodyAppend("p", "The file API isn't supported on this browser yet.");
            return;
        }

        input = document.getElementById('fileinput');
        if (!input) {
            bodyAppend("p", "Um, couldn't find the fileinput element.");
        }
        else if (!input.files) {
            bodyAppend("p", "This browser doesn't seem to support the `files` property of file inputs.");
        }
        else if (!input.files[0]) {
            bodyAppend("p", "Please select a file before clicking 'Load'");
        }
        else {
            file = input.files[0];
            fr = new FileReader();
            fr.onload = receivedText;
            fr.readAsText(file);
        }

        function receivedText() {
            showResult(fr, "Text");

            fr = new FileReader();
            fr.onload = receivedBinary;
            fr.readAsBinaryString(file);
        }

        function receivedBinary() {
            showResult(fr, "Binary");
        }
    }

    function showResult(fr, label) {
        var markup, result, n, aByte, byteStr;

        markup = [];
        result = fr.result;
        for (n = 0; n < result.length; ++n) {
            aByte = result.charCodeAt(n);
            byteStr = aByte.toString(16);
            if (byteStr.length < 2) {
                byteStr = "0" + byteStr;
            }
            markup.push(byteStr);
        }
        bodyAppend("p", label + " (" + result.length + "):");
        bodyAppend("pre", markup.join(" "));
    }

    function bodyAppend(tagName, innerHTML) {
        var elm;

        elm = document.createElement(tagName);
        elm.innerHTML = innerHTML;
        document.body.appendChild(elm);
    }

</script>
</head>
<body>
<form action='#' onsubmit="return false;">
<input type='file' id='fileinput'>
<input type='button' id='btnLoad' value='Load' onclick='loadFile();'>
</form>
</body>
</html>

Если я использую это с файлом "Testing 1 2 3", хранящимся в UTF-16, вот результаты, которые я получаю:

Text (13):

54 65 73 74 69 6e 67 20 31 20 32 20 33

Binary (28):

ff fe 54 00 65 00 73 00 74 00 69 00 6e 00 67 00 20 00 31 00 20 00 32 00 20 00 33 00

Как вы можете видеть, readAsText интерпретировал символы, и поэтому я получил 13 (длина "Тестирование 1 2 3" ), а readAsBinaryString - нет, и поэтому я получил 28 (двухбайтный BOM плюс два байта для каждого символа).


* XMLHttpRequest.response с responseType = "arraybuffer" поддерживается в HTML 5.

** "Строки JavaScript - это UTF-16" может показаться нечетным выражением; разве это не Юникод? Нет, строка JavaScript серия блоков кода UTF-16; вы видите суррогатные пары как два отдельных "персонажа" JavaScript, хотя на самом деле суррогатная пара в целом является всего лишь одним персонажем. Подробнее см. Ссылку.

Ответ 2

Я думаю, вы можете использовать readAsArrayBuffer() для получения двоичных данных.