如何在 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 像 TextDecoder
和 FileReader
。 TextDecoder
旨在将编码为文本文件(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 来解析此文件格式。
注意几点:
- 使用 DataView 时,您必须跟踪阅读的位置。它不会自动向前移动指针。
- 在
readPoints
函数中,getFloat64
的字节顺序设置为 true 以使用 Little Endian,因为文件中点的数据是反向编码的。
这应该足以弄清楚如何解析文件的其余部分。你只需要知道每个标签中的数据格式是什么即可。
我正在尝试读取一个包含标签之间数据的二进制文件(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 像 TextDecoder
和 FileReader
。 TextDecoder
旨在将编码为文本文件(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 来解析此文件格式。
注意几点:
- 使用 DataView 时,您必须跟踪阅读的位置。它不会自动向前移动指针。
- 在
readPoints
函数中,getFloat64
的字节顺序设置为 true 以使用 Little Endian,因为文件中点的数据是反向编码的。
这应该足以弄清楚如何解析文件的其余部分。你只需要知道每个标签中的数据格式是什么即可。