///|
/// The kind of object stored under an HDF5 link.
#derive(Eq, @debug.Debug)
pub(all) enum Hdf5ObjectKind {
Group
Dataset
Datatype
Unknown
}
///|
pub extend Hdf5ObjectKind with Eq::{not_equal, equal}
///|
pub extend Hdf5ObjectKind with @debug.Debug::{to_repr}
///|
/// One named object in the file hierarchy.
#derive(Eq, @debug.Debug)
pub(all) struct Hdf5Node {
path : String
address : UInt64
kind : Hdf5ObjectKind
}
///|
pub extend Hdf5Node with Eq::{not_equal, equal}
///|
pub extend Hdf5Node with @debug.Debug::{to_repr}
///|
/// A parsed HDF5 file backed by bytes or a random-access source.
pub struct Hdf5File {
priv bytes : Bytes
priv superblock : Superblock
priv source : &Hdf5Source?
priv shared_messages : SharedMessageContext
}
///|
/// Parses an HDF5 file from in-memory bytes.
pub fn Hdf5File::parse(bytes : Bytes) -> Hdf5File raise Hdf5Error {
let superblock = parse_superblock(bytes)
{
bytes,
superblock,
source: None,
shared_messages: parse_shared_message_context(bytes, None, superblock),
}
}
///|
/// Parses an HDF5 file from a random-access source.
///
pub fn Hdf5File::parse_source(source : &Hdf5Source) -> Hdf5File raise Hdf5Error {
let superblock = parse_source_superblock(source)
{
bytes: b"",
superblock,
source: Some(source),
shared_messages: parse_shared_message_context(b"", Some(source), superblock),
}
}
///|
/// Returns the parsed HDF5 superblock version.
pub fn Hdf5File::superblock_version(self : Hdf5File) -> Int32 {
self.superblock.version
}
///|
/// Returns the file address of the root group object header.
fn Hdf5File::root_address(self : Hdf5File) -> UInt64 {
self.superblock.root_group_object_header
}
///|
/// Reads an object header at a file address.
fn Hdf5File::object_header_at(
self : Hdf5File,
address : UInt64,
) -> ObjectHeader raise Hdf5Error {
let header = match self.source {
None =>
parse_object_header(
self.bytes,
address,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
Some(source) =>
parse_source_object_header(
source,
address,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
self.shared_messages.resolve_object_header(header, address)
}
///|
fn object_kind(header : ObjectHeader) -> Hdf5ObjectKind {
if header.first(msg_dataspace) is Some(_) &&
header.first(msg_datatype) is Some(_) &&
header.first(msg_data_layout) is Some(_) {
Dataset
} else if header.first(msg_symbol_table) is Some(_) ||
header.first(msg_link_info) is Some(_) ||
header.first(msg_link) is Some(_) {
Group
} else if header.first(msg_datatype) is Some(_) {
Datatype
} else {
Unknown
}
}
///|
/// Reads the links stored in a group object header, supporting both the
/// symbol-table (v1) and link-message (v2) group encodings.
fn Hdf5File::links(
self : Hdf5File,
header : ObjectHeader,
) -> Array[GroupLink] raise Hdf5Error {
match header.first(msg_symbol_table) {
Some(message) => {
let symbol_table = parse_symbol_table_message(
message,
self.superblock.offset_size,
self.superblock.length_size,
)
match self.source {
None =>
read_group_links_v1(
self.bytes,
symbol_table.btree_address,
symbol_table.heap_address,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
Some(source) =>
read_source_group_links_v1(
source,
symbol_table.btree_address,
symbol_table.heap_address,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
}
None =>
match self.source {
Some(source) =>
read_source_group_links_v2(
source,
header,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
None =>
read_group_links_v2(
self.bytes,
header,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
}
}
///|
fn split_path(path : String) -> Array[String] {
let segments : Array[String] = []
for part in path.split("/") {
let segment = part.to_owned()
if segment == "" || segment == "." {
continue
}
if segment == ".." {
if segments.length() > 0 {
segments.pop() |> ignore
}
} else {
segments.push(segment)
}
}
segments
}
///|
fn join_path(parent : String, name : String) -> String {
if parent == "/" || parent == "" {
"/" + name
} else {
parent + "/" + name
}
}
///|
/// Resolves an absolute or root-relative object path.
pub fn Hdf5File::resolve(
self : Hdf5File,
path : String,
) -> Hdf5Node raise Hdf5Error {
self.resolve_from("/", self.root_address(), path, 0)
}
///|
fn Hdf5File::resolve_from(
self : Hdf5File,
base_path : String,
base_address : UInt64,
path : String,
depth : Int32,
) -> Hdf5Node raise Hdf5Error {
guard depth < 16 else {
raise InvalidField("too many soft-link indirections")
}
let segments = split_path(path)
if segments.length() == 0 {
let header = self.object_header_at(base_address)
return {
path: base_path,
address: base_address,
kind: object_kind(header),
}
}
let mut current_path = base_path
let mut current_address = base_address
let mut index = 0
while index < segments.length() {
let header = self.object_header_at(current_address)
let links = self.links(header)
let segment = segments[index]
let link = match links.search_by(link => link.name == segment) {
Some(found) => links[found]
None => raise InvalidField("path component not found: " + segment)
}
match link.link_target {
Some(target) => {
if link.cache_type == 64U {
let target_path = match link.external_path {
Some(value) => value
None => ""
}
raise UnsupportedFeature(
"external links are not resolved: " + target + ":" + target_path,
)
}
let rebased = if target.has_prefix("/") {
target
} else {
join_path(current_path, target)
}
let mut remaining = ""
let mut next = index + 1
while next < segments.length() {
remaining = join_path(remaining, segments[next])
next = next + 1
}
let full_path = if remaining == "" {
rebased
} else {
join_path(rebased, remaining)
}
return self.resolve_from("/", self.root_address(), full_path, depth + 1)
}
None => {
current_path = join_path(current_path, segment)
current_address = link.object_header_address
index = index + 1
}
}
}
let header = self.object_header_at(current_address)
{ path: current_path, address: current_address, kind: object_kind(header), }
}
///|
/// Reads a dataset and decodes all of its values.
pub fn Hdf5File::read_dataset(
self : Hdf5File,
path : String,
) -> Dataset raise Hdf5Error {
let node = self.resolve(path)
guard node.kind == Dataset else {
raise InvalidField("object is not a dataset: " + path)
}
let header = self.object_header_at(node.address)
let metadata = read_dataset_metadata(
header,
self.superblock.offset_size,
self.superblock.length_size,
)
match self.source {
Some(source) => {
let rank = metadata.dataspace.dimensions.length()
let start = Array::make(rank, 0UL)
let stride = Array::make(rank, 1UL)
let selected = read_source_dataset_selection(
source,
header,
metadata,
Hyperslab(start, metadata.dataspace.dimensions.copy(), stride),
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
{
dataspace: metadata.dataspace,
datatype: metadata.datatype,
layout: metadata.layout,
values: selected.values,
}
}
None =>
read_dataset(
self.bytes,
header,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
}
///|
/// Reads a dataset's shape, datatype and storage layout without loading its
/// values.
pub fn Hdf5File::read_dataset_metadata(
self : Hdf5File,
path : String,
) -> DatasetMetadata raise Hdf5Error {
let node = self.resolve(path)
guard node.kind == Dataset else {
raise InvalidField("object is not a dataset: " + path)
}
let header = self.object_header_at(node.address)
read_dataset_metadata(
header,
self.superblock.offset_size,
self.superblock.length_size,
)
}
///|
/// Reads a hyperslab or point selection from a dataset.
pub fn Hdf5File::read_dataset_selection(
self : Hdf5File,
path : String,
selection : DatasetSelection,
) -> DatasetSelectionResult raise Hdf5Error {
let node = self.resolve(path)
guard node.kind == Dataset else {
raise InvalidField("object is not a dataset: " + path)
}
let header = self.object_header_at(node.address)
let metadata = read_dataset_metadata(
header,
self.superblock.offset_size,
self.superblock.length_size,
)
match self.source {
Some(source) =>
read_source_dataset_selection(
source,
header,
metadata,
selection,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
None =>
read_dataset_selection(
self.bytes,
header,
metadata,
selection,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
}
///|
/// Reads the attributes attached to an object path.
pub fn Hdf5File::attributes(
self : Hdf5File,
path : String,
) -> Array[Attribute] raise Hdf5Error {
let node = self.resolve(path)
let header = self.object_header_at(node.address)
match self.source {
Some(source) =>
read_source_attributes(
source,
header,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
shared_messages=Some(self.shared_messages),
)
None =>
read_attributes(
self.bytes,
header,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
shared_messages=Some(self.shared_messages),
)
}
}
///|
fn Hdf5File::walk_group(
self : Hdf5File,
path : String,
address : UInt64,
ancestors : Array[UInt64],
nodes : Array[Hdf5Node],
depth : Int32,
) -> Unit raise Hdf5Error {
guard depth < 128 else { raise InvalidField("group hierarchy is too deep") }
if ancestors.contains(address) {
return
}
ancestors.push(address)
let header = self.object_header_at(address)
for link in self.links(header) {
let child_path = join_path(path, link.name)
match link.link_target {
Some(_) =>
if link.cache_type == 64U {
nodes.push({ path: child_path, address: 0UL, kind: Unknown, })
} else {
let child = self.resolve(child_path) catch {
_ => { path: child_path, address: 0UL, kind: Unknown, }
}
nodes.push({
path: child_path,
address: child.address,
kind: child.kind,
})
}
None => {
let child_header = self.object_header_at(link.object_header_address)
let kind = object_kind(child_header)
nodes.push({
path: child_path,
address: link.object_header_address,
kind,
})
if kind == Group {
self.walk_group(
child_path,
link.object_header_address,
ancestors,
nodes,
depth + 1,
)
}
}
}
}
ancestors.pop() |> ignore
}
///|
/// Lists the full object hierarchy below the root group.
pub fn Hdf5File::walk(self : Hdf5File) -> Array[Hdf5Node] raise Hdf5Error {
let nodes : Array[Hdf5Node] = []
self.walk_group("/", self.root_address(), [], nodes, 0)
nodes
}