// Experimental HDF5 JavaScript reader var hdf5 = hdf5 || {}; var zip = zip || require('./zip'); hdf5.File = class { static open(data) { const buffer = data instanceof Uint8Array ? data : data.peek(); const reader = new hdf5.Reader(buffer, 0); if (reader.match('\x89HDF\r\n\x1A\n')) { return new hdf5.File(reader); } return null; } constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html this._globalHeap = new hdf5.GlobalHeap(reader); const version = reader.byte(); switch (version) { case 0: case 1: { this._freeSpaceStorageVersion = reader.byte(); this._rootGroupEntryVersion = reader.byte(); reader.skip(1); this._sharedHeaderMessageVersionFormat = reader.byte(); reader.initialize(); reader.skip(1); this._groupLeafNodeK = reader.uint16(); // 0x04? this._groupInternalNodeK = reader.uint16(); // 0x10? reader.skip(4); if (version > 0) { this._indexedStorageInternalNodeK = reader.uint16(); this.seek(2); // Reserved } this._baseAddress = reader.offset(); reader.offset(); // Address of File Free space Info this._endOfFileAddress = reader.offset(); reader.offset(); // Driver Information Block Address if (this._baseAddress != 0) { throw new hdf5.Error('Base address is not zero.'); } const rootGroupEntry = new hdf5.SymbolTableEntry(reader); this._rootGroup = new hdf5.Group(reader, rootGroupEntry, null, this._globalHeap, '', ''); break; } case 2: case 3: { reader.initialize(); reader.byte(); this._baseAddress = reader.offset(); this._superBlockExtensionAddress = reader.offset(); this._endOfFileAddress = reader.offset(); const rootGroupObjectHeader = new hdf5.DataObjectHeader(reader.at(reader.offset())); this._rootGroup = new hdf5.Group(reader, null, rootGroupObjectHeader, this._globalHeap, '', ''); break; } default: throw new hdf5.Error('Unsupported Superblock version ' + version + '.'); } } get rootGroup() { return this._rootGroup; } }; hdf5.Group = class { constructor(reader, entry, objectHeader, globalHeap, parentPath, name) { this._reader = reader; this._entry = entry; this._dataObjectHeader = objectHeader; this._globalHeap = globalHeap; this._name = name; this._path = parentPath == '/' ? (parentPath + name) : (parentPath + '/' + name); } get name() { return this._name; } get path() { return this._path; } group(path) { this._decodeGroups(); if (this._groups.has(path)) { return this._groups.get(path); } const index = path.indexOf('/'); if (index !== -1) { const group = this.group(path.substring(0, index)); if (group) { return group.group(path.substring(index + 1)); } } return null; } get groups() { this._decodeGroups(); return this._groups; } get attributes() { this._decodeDataObject(); return this._attributes; } get value() { this._decodeDataObject(); return this._value; } _decodeDataObject() { if (!this._dataObjectHeader) { const reader = this._reader.at(this._entry.objectHeaderAddress); this._dataObjectHeader = new hdf5.DataObjectHeader(reader); } if (!this._attributes) { this._attributes = new Map(); for (const attribute of this._dataObjectHeader.attributes) { const name = attribute.name; const value = attribute.decodeValue(this._globalHeap); this._attributes.set(name, value); } this._value = null; const datatype = this._dataObjectHeader.datatype; const dataspace = this._dataObjectHeader.dataspace; const dataLayout = this._dataObjectHeader.dataLayout; const filterPipeline = this._dataObjectHeader.filterPipeline; if (datatype && dataspace && dataLayout) { this._value = new hdf5.Variable(this._reader, this._globalHeap, datatype, dataspace, dataLayout, filterPipeline); } } } _decodeGroups() { if (!this._groups) { this._groups = new Map(); if (this._entry) { if (this._entry.treeAddress || this._entry.heapAddress) { const heap = new hdf5.Heap(this._reader.at(this._entry.heapAddress)); const tree = new hdf5.Tree(this._reader.at(this._entry.treeAddress)); for (const node of tree.nodes) { for (const entry of node.entries) { const name = heap.getString(entry.linkNameOffset); const group = new hdf5.Group(this._reader, entry, null, this._globalHeap, this._path, name); this._groups.set(name, group); } } } } else { this._decodeDataObject(); for (const link of this._dataObjectHeader.links) { if (Object.prototype.hasOwnProperty.call(link, 'objectHeaderAddress')) { const name = link.name; const objectHeader = new hdf5.DataObjectHeader(this._reader.at(link.objectHeaderAddress)); const linkGroup = new hdf5.Group(this._reader, null, objectHeader, this._globalHeap, this._path, name); this._groups.set(name, linkGroup); } } } } } }; hdf5.Variable = class { constructor(reader, globalHeap, datatype, dataspace, dataLayout, filterPipeline) { this._reader = reader; this._globalHeap = globalHeap; this._datatype = datatype; this._dataspace = dataspace; this._dataLayout = dataLayout; this._filterPipeline = filterPipeline; } get type () { return this._datatype.type; } get littleEndian() { return this._datatype.littleEndian; } get shape() { return this._dataspace.shape; } get value() { const data = this.data; if (data) { const reader = new hdf5.Reader(data); const array = this._dataspace.read(this._datatype, reader); return this._dataspace.decode(this._datatype, array, array, this._globalHeap); } return null; } get data() { switch (this._dataLayout.layoutClass) { case 1: // Contiguous if (this._dataLayout.address) { return this._reader.at(this._dataLayout.address).read(this._dataLayout.size); } break; case 2: { // Chunked const dimensionality = this._dataLayout.dimensionality; if (dimensionality === 2) { const tree = new hdf5.Tree(this._reader.at(this._dataLayout.address), dimensionality); const itemsize = this._dataLayout.datasetElementSize; const shape = this._dataspace.shape; const size = shape.reduce((a, b) => a * b, 1) * itemsize; const data = new Uint8Array(size); for (const node of tree.nodes) { if (node.filterMask !== 0) { return null; } const start = node.fields.slice(0, 1).reduce((a, b) => a * b, 1) * itemsize; let chunk = node.data; if (this._filterPipeline) { for (const filter of this._filterPipeline.filters) { chunk = filter.decode(chunk); } } data.set(chunk, start); } return data; } break; } default: { throw new hdf5.Error("Unknown data layout class '" + this.layoutClass + "'."); } } return null; } }; hdf5.Reader = class { constructor(buffer) { if (buffer) { this._buffer = buffer; this._dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); this._position = 0; this._offset = 0; } } initialize() { this._offsetSize = this.byte(); this._lengthSize = this.byte(); } skip(offset) { this._offset += offset; if (this._position + this._offset > this._buffer.length) { throw new hdf5.Error('Expected ' + (this._position + this._offset - this._buffer.length) + ' more bytes. The file might be corrupted. Unexpected end of file.'); } } read(length) { const offset = this._offset; this.skip(length); return this._buffer.subarray(this._position + offset, this._position + this._offset); } int8() { const offset = this._offset; this.skip(1); return this._dataView.getInt8(this._position + offset); } byte() { const offset = this._offset; this.skip(1); return this._dataView.getUint8(this._position + offset); } int16() { const offset = this._position + this._offset; this.skip(2); return this._dataView.getInt16(offset, true); } uint16() { const offset = this._position + this._offset; this.skip(2); return this._dataView.getUint16(offset, true); } int32() { const offset = this._position + this._offset; this.skip(4); return this._dataView.getInt32(offset, true); } uint32() { const offset = this._position + this._offset; this.skip(4); return this._dataView.getUint32(offset, true); } int64() { const offset = this._position + this._offset; this.skip(8); return this._dataView.getInt64(offset, true).toNumber(); } uint64() { const offset = this._position + this._offset; this.skip(8); return this._dataView.getUint64(offset, true).toNumber(); } uint(type) { switch (type) { case 0: return this.byte(); case 1: return this.uint16(); case 2: return this.uint32(); case 3: return this.uint64(); } } float16() { const offset = this._offset; this.skip(2); const value = this._dataView.getUint16(this._position + offset, true); // decode float16 value const s = (value & 0x8000) >> 15; const e = (value & 0x7C00) >> 10; const f = value & 0x03FF; if(e == 0) { return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10)); } else if (e == 0x1F) { return f ? NaN : ((s ? -1 : 1) * Infinity); } return (s ? -1 : 1) * Math.pow(2, e-15) * (1 + (f / Math.pow(2, 10))); } float32() { const offset = this._position + this._offset; this.skip(4); return this._dataView.getFloat32(offset, true); } float64() { const offset = this._position + this._offset; this.skip(8); return this._dataView.getFloat64(offset, true); } string(size, encoding) { if (!size || size == -1) { let position = this._position + this._offset; while (this._buffer[position] != 0) { position++; } size = position - this._position - this._offset + 1; } const data = this.read(size); return hdf5.Reader.decode(data, encoding); } static decode(data, encoding) { let content = ''; if (encoding == 'utf-8') { if (!hdf5.Reader._utf8Decoder) { hdf5.Reader._utf8Decoder = new TextDecoder('utf-8'); } content = hdf5.Reader._utf8Decoder.decode(data); } else { if (!hdf5.Reader._asciiDecoder) { hdf5.Reader._asciiDecoder = new TextDecoder('ascii'); } content = hdf5.Reader._asciiDecoder.decode(data); } return content.replace(/\0/g, ''); } offset() { switch (this._offsetSize) { case 8: { const position = this._position + this._offset; this.skip(8); const value = this._dataView.getUint64(position, true); if (value.low === -1 && value.high === -1) { return undefined; } return value.toNumber(); } case 4: { const value = this.uint32(); if (value === 0xffffffff) { return undefined; } return value; } } throw new hdf5.Error('Unsupported offset size \'' + this._offsetSize + '\'.'); } length() { switch (this._lengthSize) { case 8: { const position = this._position + this._offset; this.skip(8); const value = this._dataView.getUint64(position, true); if (value.low === -1 && value.high === -1) { return undefined; } return value.toNumber(); } case 4: { const value = this.uint32(); if (value === 0xffffffff) { return undefined; } return value; } } throw new hdf5.Error('Unsupported length size \'' + this._lengthSize + '\'.'); } at(position) { const reader = new hdf5.Reader(null); reader._buffer = this._buffer; reader._dataView = this._dataView; reader._position = position; reader._offset = 0; reader._offsetSize = this._offsetSize; reader._lengthSize = this._lengthSize; return reader; } clone() { const reader = new hdf5.Reader(this._buffer, this._position); reader._buffer = this._buffer; reader._dataView = this._dataView; reader._position = this._position; reader._offset = this._offset; reader._offsetSize = this._offsetSize; reader._lengthSize = this._lengthSize; return reader; } align(mod) { if (this._offset % mod != 0) { this._offset = (Math.floor(this._offset / mod) + 1) * mod; } } match(text) { if (this._position + this._offset + text.length > this._buffer.length) { return false; } const offset = this._offset; const buffer = this.read(text.length); for (let i = 0; i < text.length; i++) { if (text.charCodeAt(i) != buffer[i]) { this._offset = offset; return false; } } return true; } get position() { return this._position + this._offset; } get size() { return this._buffer.length; } }; hdf5.SymbolTableNode = class { constructor(reader) { if (!reader.match('SNOD')) { throw new hdf5.Error("Not a valid 'SNOD' block."); } const version = reader.byte(); if (version == 1) { reader.skip(1); const entriesUsed = reader.uint16(); this.entries = []; for (let i = 0; i < entriesUsed; i++) { const entry = new hdf5.SymbolTableEntry(reader); this.entries.push(entry); } } else { throw new hdf5.Error('Unsupported symbol table node version \'' + version + '\'.'); } } }; hdf5.SymbolTableEntry = class { constructor(reader) { this.linkNameOffset = reader.offset(); this.objectHeaderAddress = reader.offset(); const cacheType = reader.uint32(); reader.skip(4); // Reserved switch (cacheType) { case 0: break; case 1: { const scratchReader = reader.clone(); this.treeAddress = scratchReader.offset(); this.heapAddress = scratchReader.offset(); break; } default: throw new hdf5.Error('Unsupported cache type \'' + cacheType + '\'.'); } reader.skip(16); // Scratch-pad space } }; hdf5.DataObjectHeader = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#ObjectHeader this.attributes = []; this.links = []; this.continuations = []; const version = reader.match('OHDR') ? reader.byte() : reader.byte(); switch (version) { case 1: { reader.skip(1); const messageCount = reader.uint16(); reader.uint32(); const objectHeaderSize = reader.uint32(); reader.align(8); let end = reader.position + objectHeaderSize; for (let i = 0; i < messageCount; i++) { const type = reader.uint16(); const size = reader.uint16(); const flags = reader.byte(); reader.skip(3); reader.align(8); const next = this._readMessage(reader, type, size, flags); if ((!next || reader.position >= end) && this.continuations.length > 0) { const continuation = this.continuations.shift(); reader = reader.at(continuation.offset); end = continuation.offset + continuation.length; } else { reader.align(8); } } break; } case 2: { const flags = reader.byte(); if ((flags & 0x20) != 0) { reader.uint32(); // access time reader.uint32(); // modification time reader.uint32(); // change time reader.uint32(); // birth time } if ((flags & 0x10) != 0) { reader.uint16(); // max compact attributes reader.uint16(); // min compact attributes } const order = (flags & 0x04) != 0; const size = reader.uint(flags & 0x03); let next = true; let end = reader.position + size; while (next && reader.position < end) { const type = reader.byte(); const size = reader.uint16(); const flags = reader.byte(); if (reader.position < end) { if (order) { reader.uint16(); // creation order } next = this._readMessage(reader, type, size, flags); } if ((!next || reader.position >= end) && this.continuations.length > 0) { const continuation = this.continuations.shift(); reader = reader.at(continuation.offset); end = continuation.offset + continuation.length; if (!reader.match('OCHK')) { throw new hdf5.Error('Invalid continuation block signature.'); } next = true; } } break; } default: { throw new hdf5.Error("Unsupported data object header version '" + version + "'."); } } } _readMessage(reader, type, size, flags) { switch(type) { case 0x0000: // NIL return false; case 0x0001: // Dataspace this.dataspace = (size != 4 || flags != 1) ? new hdf5.Dataspace(reader.clone()) : null; break; case 0x0002: // Link Info this.linkInfo = new hdf5.LinkInfo(reader.clone()); break; case 0x0003: // Datatype this.datatype = new hdf5.Datatype(reader.clone()); break; case 0x0004: case 0x0005: // Fill Value this.fillValue = new hdf5.FillValue(reader.clone(), type); break; case 0x0006: // Link this.links.push(new hdf5.Link(reader.clone())); break; case 0x0008: // Data Layout this.dataLayout = new hdf5.DataLayout(reader.clone()); break; case 0x000A: // Group Info this.groupInfo = new hdf5.GroupInfo(reader.clone()); break; case 0x000B: // Filter Pipeline this.filterPipeline = new hdf5.FilterPipeline(reader.clone()); break; case 0x000C: // Attribute this.attributes.push(new hdf5.Attribute(reader.clone())); break; case 0x000D: // Object Comment Message this.comment = reader.string(-1, 'ascii'); break; case 0x0010: // Object Header Continuation this.continuations.push(new hdf5.ObjectHeaderContinuation(reader.clone())); break; case 0x0011: // Symbol Table this.symbolTable = new hdf5.SymbolTable(reader.clone()); break; case 0x000E: // Object Modification Time (Old) case 0x0012: // Object Modification Time this.objectModificationTime = new hdf5.ObjectModificationTime(reader.clone(), type); break; case 0x0015: // Attribute Info this.attributeInfo = new hdf5.AttributeInfo(reader.clone()); break; default: throw new hdf5.Error('Unsupported message type \'' + type + '\'.'); } reader.skip(size); return true; } }; hdf5.Message = class { constructor(type, data, flags) { this._type = type; this._data = data; this._flags = flags; } }; hdf5.Dataspace = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#DataspaceMessage this._sizes = []; const version = reader.byte(); switch (version) { case 1: this._dimensions = reader.byte(); this._flags = reader.byte(); reader.skip(1); reader.skip(4); for (let i = 0; i < this._dimensions; i++) { this._sizes.push(reader.length()); } if ((this._flags & 0x01) != 0) { this._maxSizes = []; for (let j = 0; j < this._dimensions; j++) { this._maxSizes.push(reader.length()); if (this._maxSizes[j] != this._sizes[j]) { throw new hdf5.Error('Max size is not supported.'); } } } if ((this._flags & 0x02) != 0) { throw new hdf5.Error('Permutation indices not supported.'); } break; case 2: this._dimensions = reader.byte(); this._flags = reader.byte(); this._type = reader.byte(); // 0 scalar, 1 simple, 2 null for (let k = 0; k < this._dimensions; k++) { this._sizes.push(reader.length()); } if ((this._flags & 0x01) != 0) { this._maxSizes = []; for (let l = 0; l < this._dimensions; l++) { this._maxSizes.push(reader.length()); } } break; default: throw new hdf5.Error("Unsupported dataspace message version '" + version + "'."); } } get shape() { return this._sizes; } read(datatype, reader) { if (this._dimensions == 0) { return datatype.read(reader); } return this._readArray(datatype, reader, this._sizes, 0); } _readArray(datatype, reader, shape, dimension) { const array = []; const size = shape[dimension]; if (dimension == shape.length - 1) { for (let i = 0; i < size; i++) { array.push(datatype.read(reader)); } } else { for (let j = 0; j < size; j++) { array.push(this._readArray(datatype, reader, shape, dimension + 1)); } } return array; } decode(datatype, data, globalHeap) { if (this._dimensions == 0) { return datatype.decode(data, globalHeap); } return this._decodeArray(datatype, data, globalHeap, this._sizes, 0); } _decodeArray(datatype, data, globalHeap, shape, dimension) { const size = shape[dimension]; if (dimension == shape.length - 1) { for (let i = 0; i < size; i++) { data[i] = datatype.decode(data[i], globalHeap); } } else { for (let j = 0; j < size; j++) { data[j] = this._decodeArray(datatype, data[j], shape, dimension + 1); } } return data; } }; hdf5.LinkInfo = class { constructor(reader) { const version = reader.byte(); switch (version) { case 0: { const flags = reader.byte(); if ((flags & 1) != 0) { this.maxCreationIndex = reader.uint64(); } this.fractalHeapAddress = reader.offset(); this.nameIndexTreeAddress = reader.offset(); if ((flags & 2) != 0) { this.creationOrderIndexTreeAddress = reader.offset(); } break; } default: throw new hdf5.Error("Unsupported link info message version '" + version + "'."); } } }; hdf5.Datatype = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#DatatypeMessage const format = reader.byte(); const version = format >> 4; this._class = format & 0xf; switch (version) { case 1: case 2: { this._flags = reader.byte() | reader.byte() << 8 | reader.byte() << 16; this._size = reader.uint32(); switch (this._class) { case 0: { this._bitOffset = reader.uint16(); this._bitPrecision = reader.uint16(); break; } case 8: { this._base = new hdf5.Datatype(reader); this._names = []; this._values = []; const count = this._flags & 0xffff; for (let i = 0; i < count; i++) { const name = reader.clone().string(-1, 'ascii'); this._names.push(name); reader.skip(Math.round((name.length + 1) / 8) * 8); } for (let i = 0; i < count; i++) { this._values.push(this._base.read(reader)); } break; } } break; } default: throw new hdf5.Error('Unsupported datatype version \'' + version + '\'.'); } } get type() { switch (this._class) { case 0: // fixed-point if ((this._flags & 0xfff6) === 0) { if ((this._flags && 0x08) !== 0) { switch (this._size) { case 1: return 'int8'; case 2: return 'int16'; case 4: return 'int32'; case 8: return 'int64'; } } else { switch (this._size) { case 1: return 'uint8'; case 2: return 'uint16'; case 4: return 'uint32'; case 8: return 'uint64'; } } } break; case 1: // floating-point if (this._size == 2 && this._flags == 0x0f20) { return 'float16'; } else if (this._size == 4 && this._flags == 0x1f20) { return 'float32'; } else if (this._size == 8 && this._flags == 0x3f20) { return 'float64'; } break; case 3: // string return 'string'; case 5: // opaque return 'uint8[]'; case 8: // enumerated if (this._base.type === 'int8' && this._names.length === 2 && this._names[0] === 'FALSE' && this._names[1] === 'TRUE' && this._values.length === 2 && this._values[0] === 0 && this._values[1] === 1) { return 'boolean'; } break; case 9: // variable-length if ((this._flags & 0x0f) == 1) { // type return 'char[]'; } break; } throw new hdf5.Error('Unsupported datatype class \'' + this._class + '\'.'); } get littleEndian() { switch (this._class) { case 0: // fixed-point case 1: // floating-point return (this.flags & 0x01) == 0; } return true; } read(reader) { switch (this._class) { case 0: // fixed-point if (this._size == 1) { return ((this._flags & 0x8) != 0) ? reader.int8() : reader.byte(); } else if (this._size == 2) { return ((this._flags & 0x8) != 0) ? reader.int16() : reader.uint16(); } else if (this._size == 4) { return ((this._flags & 0x8) != 0) ? reader.int32() : reader.uint32(); } else if (this._size == 8) { return ((this._flags & 0x8) != 0) ? reader.int64() : reader.uint64(); } throw new hdf5.Error('Unsupported fixed-point datatype.'); case 1: // floating-point if (this._size == 2 && this._flags == 0x0f20) { return reader.float16(); } else if (this._size == 4 && this._flags == 0x1f20) { return reader.float32(); } else if (this._size == 8 && this._flags == 0x3f20) { return reader.float64(); } throw new hdf5.Error('Unsupported floating-point datatype.'); case 3: // string switch ((this._flags >> 8) & 0x0f) { // character set case 0: return hdf5.Reader.decode(reader.read(this._size), 'ascii'); case 1: return hdf5.Reader.decode(reader.read(this._size), 'utf-8'); } throw new hdf5.Error('Unsupported character encoding.'); case 5: // opaque return reader.read(this._size); case 8: // enumerated return reader.read(this._size); case 9: // variable-length return { length: reader.uint32(), globalHeapID: new hdf5.GlobalHeapID(reader) }; } throw new hdf5.Error('Unsupported datatype class \'' + this._class + '\'.'); } decode(data, globalHeap) { switch (this._class) { case 0: // fixed-point return data; case 1: // floating-point return data; case 3: // string return data; case 5: // opaque return data; case 8: // enumerated return data; case 9: { // variable-length const globalHeapObject = globalHeap.get(data.globalHeapID); if (globalHeapObject != null) { const characterSet = (this._flags >> 8) & 0x0f; switch (characterSet) { case 0: return hdf5.Reader.decode(globalHeapObject.data, 'ascii'); case 1: return hdf5.Reader.decode(globalHeapObject.data, 'utf-8'); } throw new hdf5.Error('Unsupported character encoding.'); } break; } default: throw new hdf5.Error('Unsupported datatype class \'' + this._class + '\'.'); } return null; } }; hdf5.FillValue = class { constructor(reader, type) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#FillValueMessage switch (type) { case 0x0004: { const size = reader.uint32(); this.data = reader.read(size); break; } case 0x0005: default: { const version = reader.byte(); switch (version) { case 1: case 2: { reader.byte(); reader.byte(); const valueDefined = reader.byte(); if (version === 1 || valueDefined === 1) { const size = reader.uint32(); this.data = reader.read(size); } break; } case 3: { const flags = reader.byte(); if ((flags & 0x20) !== 0) { const size = reader.uint32(); this.data = reader.read(size); } break; } default: throw new hdf5.Error('Unsupported fill value version \'' + version + '\'.'); } break; } } } }; hdf5.Link = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#FillValueMessage const version = reader.byte(); switch (version) { case 1: { const flags = reader.byte(); this.type = (flags & 0x08) != 0 ? reader.byte() : 0; if ((flags & 0x04) != 0) { this.creationOrder = reader.uint32(); } const encoding = ((flags & 0x10) != 0 && reader.byte() == 1) ? 'utf-8' : 'ascii'; this.name = reader.string(reader.uint(flags & 0x03), encoding); switch (this.type) { case 0: // hard link this.objectHeaderAddress = reader.offset(); break; case 1: // soft link break; } break; } default: throw new hdf5.Error('Unsupported link message version \'' + version + '\'.'); } } }; hdf5.DataLayout = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#LayoutMessage const version = reader.byte(); switch (version) { case 1: case 2: { this.dimensionality = reader.byte(); this.layoutClass = reader.byte(); reader.skip(5); switch (this.layoutClass) { case 1: this.address = reader.offset(); this.dimensionSizes = []; for (let i = 0; i < this.dimensionality - 1; i++) { this.dimensionSizes.push(reader.int32()); } break; case 2: // Chunked this.address = reader.offset(); this.dimensionSizes = []; for (let i = 0; i < this.dimensionality - 1; i++) { this.dimensionSizes.push(reader.int32()); } this.datasetElementSize = reader.int32(); break; default: throw new hdf5.Error('Unsupported data layout class \'' + this.layoutClass + '\'.'); } break; } case 3: { this.layoutClass = reader.byte(); switch (this.layoutClass) { case 0: // Compact this.size = reader.uint16(); reader.skip(2); this.address = reader.position; break; case 1: // Contiguous this.address = reader.offset(); this.size = reader.length(); break; case 2: // Chunked this.dimensionality = reader.byte(); this.address = reader.offset(); this.dimensionSizes = []; for (let i = 0; i < this.dimensionality - 1; i++) { this.dimensionSizes.push(reader.int32()); } this.datasetElementSize = reader.int32(); break; default: throw new hdf5.Error('Unsupported data layout class \'' + this.layoutClass + '\'.'); } break; } default: throw new hdf5.Error('Unsupported data layout version \'' + version + '\'.'); } } }; hdf5.GroupInfo = class { constructor(reader) { const version = reader.byte(); switch (version) { case 0: { const flags = reader.byte(); if ((flags & 0x01) != 0) { this.maxCompactLinks = reader.uint16(); this.minDenseLinks = reader.uint16(); } if ((flags & 0x02) != 0) { this.estimatedEntriesNumber = reader.uint16(); this.estimatedLinkNameLengthEntires = reader.uint16(); } break; } default: throw new hdf5.Error('Unsupported group info version \'' + version + '\'.'); } } }; hdf5.FilterPipeline = class { constructor(reader) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#FilterMessage const version = reader.byte(); switch (version) { case 1: { this.filters = []; const numberOfFilters = reader.byte(); reader.skip(2); reader.skip(4); for (let i = 0; i < numberOfFilters; i++) { this.filters.push(new hdf5.Filter(reader)); reader.align(8); } break; } default: throw new hdf5.Error('Unsupported filter pipeline message version \'' + version + '\'.'); } } }; hdf5.Filter = class { constructor(reader) { this.id = reader.int16(); const nameLength = reader.int16(); this.flags = reader.int16(); const clientDataSize = reader.int16(); this.name = reader.string(nameLength, 'ascii'); this.clientData = reader.read(clientDataSize * 4); } decode(data) { switch (this.id) { case 1: { // gzip const archive = zip.Archive.open(data); return archive.entries.get('').peek(); } default: throw hdf5.Error("Unsupported filter '" + this.name + "'."); } } }; hdf5.Attribute = class { constructor(reader) { const version = reader.byte(); switch (version) { case 1: { reader.skip(1); const nameSize = reader.uint16(); const datatypeSize = reader.uint16(); const dataspaceSize = reader.uint16(); this.name = reader.string(nameSize, 'utf-8'); reader.align(8); this._datatype = new hdf5.Datatype(reader.clone()); reader.skip(datatypeSize); reader.align(8); this._dataspace = new hdf5.Dataspace(reader.clone()); reader.skip(dataspaceSize); reader.align(8); this._data = this._dataspace.read(this._datatype, reader); break; } case 3: { reader.byte(); const nameSize = reader.uint16(); const datatypeSize = reader.uint16(); const dataspaceSize = reader.uint16(); const encoding = reader.byte() == 1 ? 'utf-8' : 'ascii'; this.name = reader.string(nameSize, encoding); this._datatype = new hdf5.Datatype(reader.clone()); reader.skip(datatypeSize); this._dataspace = new hdf5.Dataspace(reader.clone()); reader.skip(dataspaceSize); this._data = this._dataspace.read(this._datatype, reader); break; } default: throw new hdf5.Error('Unsupported attribute message version \'' + version + '\'.'); } } decodeValue(globalHeap) { if (this._data) { return this._dataspace.decode(this._datatype, this._data, globalHeap); } return null; } }; hdf5.ObjectHeaderContinuation = class { constructor(reader) { this.offset = reader.offset(); this.length = reader.length(); } }; hdf5.SymbolTable = class { constructor(reader) { this.treeAddress = reader.offset(); // hdf5.Tree pointer this.heapAddress = reader.offset(); // hdf5.Heap pointer } }; hdf5.ObjectModificationTime = class { constructor(reader, type) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#ModificationTimeMessage switch (type) { case 0x000E: { this.year = reader.uint32(); this.month = reader.uint16(); this.day = reader.uint16(); this.hour = reader.uint16(); this.minute = reader.uint16(); this.second = reader.uint16(); reader.skip(2); break; } case 0x0012: { const version = reader.byte(); reader.skip(3); switch (version) { case 1: this.timestamp = reader.uint32(); break; default: throw new hdf5.Error('Unsupported object modification time message version \'' + version + '\'.'); } break; } } } }; hdf5.AttributeInfo = class { constructor(reader) { const version = reader.byte(); switch (version) { case 0: { const flags = reader.byte(); if ((flags & 1) != 0) { this.maxCreationIndex = reader.uint64(); } this.fractalHeapAddress = reader.offset(); this.attributeNameTreeAddress = reader.offset(); if ((flags & 2) != 0) { this.attributeCreationOrderTreeAddress = reader.offset(); } break; } default: throw new hdf5.Error('Unsupported attribute info message version \'' + version + '\'.'); } } }; hdf5.Tree = class { constructor(reader, dimensionality) { // https://support.hdfgroup.org/HDF5/doc/H5.format.html#V1Btrees if (!reader.match('TREE')) { throw new hdf5.Error("Not a valid 'TREE' block."); } this.type = reader.byte(); this.level = reader.byte(); const entriesUsed = reader.uint16(); reader.offset(); // address of left sibling reader.offset(); // address of right sibling this.nodes = []; switch (this.type) { case 0: // Group nodes for (let i = 0; i < entriesUsed; i++) { reader.length(); const childPointer = reader.offset(); if (this.level == 0) { const node = new hdf5.SymbolTableNode(reader.at(childPointer)); this.nodes.push(node); } else { const tree = new hdf5.Tree(reader.at(childPointer)); this.nodes.push(...tree.nodes); } } break; case 1: // Raw data chunk nodes for (let i = 0; i < entriesUsed; i++) { const size = reader.int32(); const filterMask = reader.int32(); const fields = []; for (let j = 0; j < dimensionality; j++) { fields.push(reader.uint64()); } const childPointer = reader.offset(); if (this.level == 0) { const data = reader.at(childPointer).read(size); this.nodes.push({ data: data, fields: fields, filterMask: filterMask }); } else { const tree = new hdf5.Tree(reader.at(childPointer), dimensionality); this.nodes.push(...tree.nodes); } } break; default: throw new hdf5.Error('Unsupported B-Tree node type \'' + this.type + '\'.'); } } }; hdf5.Heap = class { constructor(reader) { this._reader = reader; if (!reader.match('HEAP')) { throw new hdf5.Error("Not a valid 'HEAP' block."); } const version = reader.byte(); switch (version) { case 0: { reader.skip(3); this._dataSize = reader.length(); this._offsetToHeadOfFreeList = reader.length(); this._dataAddress = reader.offset(); break; } default: { throw new hdf5.Error('Unsupported Local Heap version \'' + version + '\'.'); } } } getString(offset) { const reader = this._reader.at(this._dataAddress + offset); return reader.string(-1, 'utf-8'); } }; hdf5.GlobalHeap = class { constructor(reader) { this._reader = reader; this._collections = new Map(); } get(globalHeapID) { const address = globalHeapID.address; if (!this._collections.has(address)) { this._collections.set(address, new hdf5.GlobalHeapCollection(this._reader.at(address))); } return this._collections.get(globalHeapID.address).getObject(globalHeapID.objectIndex); } }; hdf5.GlobalHeapCollection = class { constructor(reader) { const startPosition = reader.position; if (!reader.match('GCOL')) { throw new hdf5.Error("Not a valid 'GCOL' block."); } const version = reader.byte(); switch (version) { case 1: { reader.skip(3); this._objects = new Map(); const size = reader.length(); const endPosition = startPosition + size; while (reader.position < endPosition) { const index = reader.uint16(); if (index == 0) { break; } this._objects.set(index, new hdf5.GlobalHeapObject(reader)); reader.align(8); } break; } default: { throw new hdf5.Error('Unsupported global heap collection version \'' + version + '\'.'); } } } getObject(objectIndex) { if (this._objects.has(objectIndex)) { return this._objects.get(objectIndex); } return null; } }; hdf5.GlobalHeapObject = class { constructor(reader) { reader.uint16(); reader.skip(4); const length = reader.length(); this.data = reader.read(length); } }; hdf5.GlobalHeapID = class { constructor(reader) { this.address = reader.offset(); this.objectIndex = reader.uint32(); } }; hdf5.Error = class extends Error { constructor(message) { super(message); this.name = 'HDF5 Error'; } }; if (typeof module !== 'undefined' && typeof module.exports === 'object') { module.exports.File = hdf5.File; }