如何在 javascript 中读取二进制文件(xml 样式)?

How can I read a binary file (xml style) in javascript?

我正在尝试读取一个包含标签之间数据的二进制文件(xml 样式),我使用的页面如下:

    <!DOCTYPE html>
<html lang="en">
<body>
    <header>
        <h1>Load a File</h1>
    </header>
    <main>
        <input type="file" id="file">
    </main>  
    <script>
 function onfilechange(evt) {
    var selFile = evt.target.files[0];
    var reader = new FileReader();
    reader.onloadend = function (e) {
        var h3d =new Int8Array(e.target.result);
        console.log(h3d);
        console.log(enc.decode(h3d));
    };
    reader.readAsArrayBuffer(selFile);
}
document.getElementById('file').addEventListener('change', onfilechange);
var enc = new TextDecoder("utf-8");
</script>
</body>
</html>

我得到了这个文件的结果(第一个 Console.log):

Int8Array(1025) [9, 0, 0, 0, 8, 60, 77, 79, 68, 69, 76, 79, 62, 9, 0, 0, 0, 8, 60, 80, 79, 78, 84, 79, 83, 62, 10, 0, 0, 0, 2, 0, 0, 0, 1, 65, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 2, 0, 0, 0, 1, 66, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 68, 64, 0, 0, 0, 0, 0, 0, 36, 64, 2, 0, 0, 0, 1, 67, 0, 0, 0, 0, …]

第二个Console.log:

<MODEL>
 <POINTS>A$@$@$@B$@D@$@CN@D@$@DN@$@$@EN@$@>@FN@D@>@GD@$@I@HD@D@I@ 
 I$@$@I@J$@D@I@</POINTS>
 <FACES>ABCDADEGIIGHJAIJBHGEFFEDCJHFCB</FACES>
<SYSTEM></SYSTEM>
</MODEL>

如何获取存储在标签之间的实际数据? 我应该得到一些点坐标,例如:

<MODEL> 
   <POINTS>A,10,10,10; B,10,20,30; ...</POINTS>
   <FACES>1,A,B,C,D; 2,A,D,E,G,I;...  </FACES>
   <SYSTEM>...and some other stuff!</SYSTEM>
</MODEL>

谢谢!

这里发生了很多事情所以让我看看是否可以解释一下。

首先,您所期望的此文件的输出并不是此文件中的实际数据。如果您要创建二进制格式,则需要对其进行记录。例如 - 此文件中的点没有用逗号和分号分隔,它们只是一个单字节 Ascii 字符,后跟 3 - 8 字节数字。请注意 - 用于点的数字以与文件其余部分相反的顺序编码。

当您从 OS 加载文件时,您将获得文件的原始字节。这些字节需要转换成可用的东西,这就是为什么有 API 像 TextDecoderFileReaderTextDecoder 旨在将编码为文本文件(UTF-8、UTF-16 等)的文件转换为 JavaScript 字符串。这不是文本文件,它是二进制文件,因此您不能使用 TextDecoder

您看到输出的原因是当您在文本编辑器(控制台是文本编辑器)中显示二进制文件时,它会将每个字节显示为相应的 Ascii 字符。这就是您看到所有乱码文本的原因——它们实际上是 ascii 字符。文本编辑器不知道它们应该是 8 字节数字。

所以,基本上没有 JavaScript API 可以将这个二进制文件转换成文本,因为它不知道如何 - 它不知道它正在读取的字节代表什么.它也不知道一次读取多少字节(UTF-8 字符 1 个,UTF-16 字符 2 个或整数 4 个)。您需要手动解析此文件,这并不像看起来那么可怕。现在您使用的是 Int8Array ,它可以工作,但您必须使用数组索引,并且必须使用位移来获取数字。你应该使用 DataView 因为它提供了一个 API 来从字节流中读取不同的类型。

这是一个如何解析此文件的示例。

let position = 0;
let h3d;
function onfilechange(evt) {
    const selFile = evt.target.files[0];
    const reader = new FileReader();
    reader.onloadend = function (e) {
        h3d = new DataView(e.target.result);
        while (position < h3d.byteLength) {
            if (isOpenBracket(h3d.getUint8(position++))) {
                // we are looking to see if we are starting a closing tag
                // don't increment position here - we just want to peek
                if (isSlash(h3d.getUint8(position))) {
                    while (!isCloseBracket(h3d.getUint8(position++))) {
                        // we don't need the closing tag so we will skip over it
                        // just read forward to the closing braket
                        // after this completes the 'position' will be on the byte representing the closing tag
                    }
                    // go back to the outer while loop
                    continue;
                }
                let tag = readTag(h3d, position);
                switch (tag) {
                    case 'PONTOS':
                    case 'POINTS':
                        const points = readPoints();
                    case 'FACES':
                        const faces = readFaces();
                     
                    // add other tags you want to parse

                }
            }
        }
    };
    reader.readAsArrayBuffer(selFile);
}

document.getElementById('file').addEventListener('change', onfilechange);

function readTag() {
    const tag = [];
    // start reading tag
    // read until we find the closing bracket
    // after this completes the 'position' will be on the byte representing the closing tag
    while (!isCloseBracket(val = h3d.getUint8(position++))) {
        tag.push(val);
    }
    return String.fromCodePoint(...tag);
}

function readPoints() {
    const points = {}; // or use a Map
    // do this until we hit the openning bracket of the closing tag
    while (!isOpenBracket(val = h3d.getUint8(position++))) {
    // the points are an upper case letter followed by 3 - 8 byte numbers
    // so if we hit an uppercase letter read the next 3 - 8 byte sequences as numbers
        if (isChar(val)) {
            let arr = [];
            // we need to read these number as LittleEndian because that is how they are in the file
            arr.push(h3d.getFloat64(position, true));
            position += 8;
            arr.push(h3d.getFloat64(position, true));
            position += 8;
            arr.push(h3d.getFloat64(position, true));
            position += 8;

            points[String.fromCodePoint(val)] = arr;
        }
    }
    return points;
}

function readFaces() {
    // don't know what to do here because I don't know what the format of this data is.
}
// these functions check Ascii values - no need to covert them to strings
function isWhitesapce(value) {
    return value === 32 ||
        value === 9 ||
        value === 10 ||
        value === 11 ||
        value === 12 ||
        value === 13
}

function isOpenBracket(value) {
    return value === 60;
}
function isCloseBracket(value) {
    return value === 62;
}
function isSlash(value) {
    return value === 47;
}
function isChar(value) {
// upper case letters
    return value >= 41 && value <= 90;
}

这只是又快又脏。我会创建一个单独的 class 来解析此文件格式。

注意几点:

  1. 使用 DataView 时,您必须跟踪阅读的位置。它不会自动向前移动指针。
  2. readPoints 函数中,getFloat64 的字节顺序设置为 true 以使用 Little Endian,因为文件中点的数据是反向编码的。

这应该足以弄清楚如何解析文件的其余部分。你只需要知道每个标签中的数据格式是什么即可。