///|
pub(all) struct Attribute {
name : String
value : ArrayValue
} derive(Debug)
///|
pub extend Attribute with @debug.Debug::{to_repr}
///|
fn decode_attribute(
m : Message,
sb : Superblock,
limits : Limits,
) -> Attribute raise H5Error {
let c = cursor(m.bytes, 0, m.bytes.length())
let version = c.u8()
if version < 1 || version > 3 {
raise Unsupported("attribute version", m.at)
}
let flags = c.u8()
if flags != 0 {
raise Unsupported("shared attribute datatype/dataspace", m.at)
}
let namesize = c.index(2)
let typesize = c.index(2)
let spacesize = c.index(2)
let charset = if version == 3 { c.u8() } else { 0 }
if charset > 1 {
raise Unsupported("attribute charset", m.at)
}
let rawname = c.bytes(namesize)
if namesize == 0 || rawname[namesize - 1] != 0 {
raise Invalid("attribute name terminator", m.at)
}
let name = text(rawname[:namesize - 1].to_owned(), m.at)
if name == "" || name.contains("\u0000") {
raise Invalid("invalid attribute name", m.at)
}
if version == 1 {
c.skip((8 - namesize % 8) % 8)
}
let typedata = c.bytes(typesize)
let dtype = decode_type(cursor(typedata, 0, typedata.length()))
if version == 1 {
c.skip((8 - typesize % 8) % 8)
}
let spacedata = c.bytes(spacesize)
let space = decode_space(
cursor(spacedata, 0, spacedata.length()),
sb.size_width,
limits,
)
if version == 1 {
c.skip((8 - spacesize % 8) % 8)
}
let size = checked_mul(dtype.width(), space.elements(limits~))
if size > limits.max_metadata_bytes {
raise Limit("attribute bytes")
}
let value = ArrayValue::new(dtype, space, c.bytes(size), limits~)
{ name, value, }
}
///|
fn encode_attribute(
attribute : Attribute,
limits : Limits,
) -> Message raise H5Error {
let name = attribute.name
if name == "" || name.contains("\u0000") {
raise Invalid("invalid attribute name", 0)
}
let rawname = @utf8.encode(name)
let dtype = attribute.value.dtype.encode()
let space = attribute.value.space.encode(limits)
if rawname.length() > 65534 ||
attribute.value.raw.length() > limits.max_metadata_bytes {
raise Limit("attribute length")
}
let b = buffer()
b.u8(3)
b.u8(0)
b.uint((rawname.length() + 1).to_uint64(), 2)
b.uint(dtype.length().to_uint64(), 2)
b.uint(space.length().to_uint64(), 2)
b.u8(1)
b.bytes(rawname)
b.u8(0)
b.bytes(dtype)
b.bytes(space)
b.bytes(attribute.value.raw)
message(12, b.finish())
}
///|
pub fn H5File::attributes(
self : H5File,
path~ : String = "/",
) -> Array[Attribute] raise H5Error {
let out : Array[Attribute] = []
let names : Map[String, Bool] = Map([])
for m in self.messages(self.resolve(path)) {
if m.kind == 12 {
let attr = decode_attribute(m, self.sb, self.limits)
if names.contains(attr.name) {
raise Invalid("duplicate attribute name", m.at)
}
names[attr.name] = true
out.push(attr)
} else if m.kind == 21 {
let c = cursor(m.bytes, 0, m.bytes.length())
if c.u8() != 0 {
raise Unsupported("attribute info version", m.at)
}
let flags = c.u8()
if flags > 3 {
raise Unsupported("attribute info flags", m.at)
}
if (flags & 1) != 0 {
c.skip(2)
}
if c.address(self.sb.offset_width, self.sb.base) != None ||
c.address(self.sb.offset_width, self.sb.base) != None {
raise Unsupported("dense attributes", m.at)
}
}
}
out
}
///|
test "inline attributes roundtrip UTF8 and exact array types" {
let a : Attribute = {
name: "单位/name",
value: ArrayValue::from_strings(Scalar, ["m/s"], 8),
}
let limits = Limits::default()
let sb : Superblock = {
version: 2,
offset_width: 8,
size_width: 8,
base: 0,
root: 0,
eof: 0,
}
let decoded = decode_attribute(encode_attribute(a, limits), sb, limits)
assert_eq(decoded.name, a.name)
assert_eq(decoded.value.string_at(0), "m/s")
}