@xogeny/mat-parser
Version:
A parser for MATLAB v4 files
170 lines • 7.03 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const debug = require("debug");
const parserDebug = debug("mat-parser:parser");
var Expecting;
(function (Expecting) {
Expecting[Expecting["Header"] = 0] = "Header";
Expecting[Expecting["Name"] = 1] = "Name";
Expecting[Expecting["Row"] = 2] = "Row";
Expecting[Expecting["Nothing"] = 3] = "Nothing";
})(Expecting || (Expecting = {}));
/**
* This class takes data from an Observer that passes a sequence of buffer corresponding to the
* bytes in a MATLAB v4 file. This class parses that binary data and fires calls to a Handler
* class that can then do with that information what it wishes.
*
* The format is documented here:
*
* https://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
*/
class MatFile {
constructor(source) {
this.source = source;
this.resetState();
}
resetState() {
this.state = {
rem: Buffer.alloc(0),
expecting: Expecting.Header,
header: null,
colnum: null,
name: null,
};
}
parse(handler) {
this.resetState();
return new Promise((resolve, reject) => {
let sub = this.source.subscribe((chunk) => {
this.state.rem = Buffer.concat([this.state.rem, chunk]);
try {
while (this.processBuffer(handler))
;
}
catch (e) {
console.error("Error processing buffer: ", e);
sub.unsubscribe();
reject(e);
}
}, (err) => {
reject(err);
}, () => {
if (this.state.rem.length == 0) {
handler.eof();
resolve(undefined);
}
else
reject(new Error("Ran out of data prematurely"));
});
});
}
/**
* This method returns false if it cannot process anything else.
*/
processBuffer(handler) {
let size = this.state.rem.length;
switch (this.state.expecting) {
case Expecting.Header:
if (size < 20)
return false;
let type = this.state.rem.readInt32LE(0);
let rows = this.state.rem.readInt32LE(4);
let cols = this.state.rem.readInt32LE(8);
let imaginary = this.state.rem.readInt32BE(12);
let namelen = this.state.rem.readInt32LE(16);
let dec = type.toString();
while (dec.length < 4) {
dec = "0" + dec;
}
let M = dec[0];
let O = dec[1];
let P = dec[2];
let T = dec[3];
if (M != "0") {
throw new Error("Only big-endian architectures are currently supported");
}
if (O != "0") {
throw new Error("Expected O to be zero, but it was " + O);
}
this.state.header = {
data: types_1.dataType(P),
matrix: types_1.matrixType(T),
rows: rows,
cols: cols,
imaginary: imaginary > 0,
namelen: namelen,
};
this.state.rem = this.state.rem.slice(20);
this.state.expecting = Expecting.Name;
return true;
case Expecting.Name:
if (this.state.header == null) {
throw new Error("Reading name, but no header found...this should not happen");
}
if (size < this.state.header.namelen)
return false;
let name = this.state.rem.slice(0, this.state.header.namelen - 1).toString("ascii");
parserDebug("Header for '%s' = %o", name, this.state.header);
try {
if (handler) {
handler.start(name, this.state.header.rows, this.state.header.cols);
}
}
catch (e) {
console.error("Ignoring error in handling matrix event: ", e);
}
this.state.rem = this.state.rem.slice(this.state.header.namelen);
this.state.colnum = 0;
this.state.name = name;
this.state.expecting = Expecting.Row;
return true;
case Expecting.Row:
if (this.state.header == null)
throw new Error("Reading name, but no header found. This should not happen");
if (this.state.colnum == null)
throw new Error("Expected column number to be set, but was null. This should not happen");
if (this.state.name == null)
throw new Error("Expected matrix to be named, but found null. This should not happen");
if (size < types_1.dataSize(this.state.header.data, this.state.header.rows))
return false;
let col = [];
for (let i = 0; i < this.state.header.rows; i++) {
this.state.rem = types_1.readOne(col, this.state.header.data, this.state.rem);
}
try {
if (handler) {
handler.column(this.state.name, this.state.colnum, this.state.header.matrix, col, this.state.colnum == this.state.header.cols - 1);
}
}
catch (e) {
console.error("Ignore error in handling column event: ", e);
}
this.state.colnum++;
if (this.state.colnum == this.state.header.cols) {
let stop = false;
if (handler) {
stop = handler.end(this.state.name);
}
this.state.header = null;
this.state.colnum = null;
this.state.name = null;
if (stop) {
this.state.expecting = Expecting.Nothing;
}
else {
this.state.expecting = Expecting.Header;
}
}
return true;
case Expecting.Nothing:
this.state.name = null;
this.state.rem = new Buffer([]);
return false;
default:
throw new Error("Unexpected state: " + this.state.expecting);
}
}
}
exports.MatFile = MatFile;
//# sourceMappingURL=parser.js.map