HDF5 reader: read variable-length string attributes and datasets

ReadAttrStr and HDF5DataSet::ReadString read every string as fixed-length,
sizing the buffer by H5Tget_size. For a variable-length string (what h5py
writes for a str, UTF-8) that size is a pointer, so the read put a char*
into the buffer and returned garbage. transformation_type then never equalled
"rotation", and an EIGER master whose goniometer attributes were written that
way (depends_on, transformation_type, units on /entry/sample/transformations)
was processed as stills although it holds a valid rotation sweep.

All string reads now go through one helper, ReadHDF5String, which handles both
fixed- and variable-length strings in either character set; ReadElement<string>
already handled the variable-length case and now uses the same helper.

Test: JFJochReader_Goniometer_VariableLengthStrings reads the rotation axis
from a master with fixed-length ASCII and with variable-length UTF-8 string
attributes and depends_on dataset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-09 21:15:01 +02:00
1 parent 0457cecafa
commit badd7360ca
3 files changed
+136 -47

No files matched your search

+37 -22
View File
@@ -506,6 +506,31 @@ HDF5Object & HDF5Object::Attr(const std::string &name, const std::vector<double>
return *this;
}
std::string ReadHDF5String(hid_t type_id, const std::function<herr_t(hid_t, void *)> &read) {
if (H5Tis_variable_str(type_id) > 0) {
char *tmp = nullptr;
if (read(type_id, &tmp) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read variable-length string");
std::string out = (tmp != nullptr) ? std::string(tmp) : std::string{};
if (tmp != nullptr)
H5free_memory(tmp);
return out;
}
const size_t size = H5Tget_size(type_id);
if (size == 0)
return "";
std::string buffer(size, '\0');
if (read(type_id, buffer.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read string");
const size_t end = buffer.find('\0');
if (end != std::string::npos)
buffer.resize(end);
return buffer;
}
std::vector<double> HDF5Object::ReadAttrVec(const std::string &name) {
hid_t attr_id = H5Aopen(id, name.c_str(), H5P_DEFAULT);
if (attr_id < 0)
@@ -579,11 +604,10 @@ std::string HDF5Object::ReadAttrStr(const std::string &name) {
throw JFJochException(JFJochExceptionCategory::HDF5, "Attribute " + name + " is not a string");
}
// Get string size and read it
size_t size = H5Tget_size(type_id);
std::vector<char> buffer(size + 1, '\0'); // +1 for null terminator to be safe
if (H5Aread(attr_id, type_id, buffer.data()) < 0) {
std::string ret;
try {
ret = ReadHDF5String(type_id, [&](hid_t type, void *buffer) { return H5Aread(attr_id, type, buffer); });
} catch (...) {
H5Tclose(type_id);
H5Aclose(attr_id);
throw JFJochException(JFJochExceptionCategory::HDF5, "Cannot read attribute " + name);
@@ -591,7 +615,7 @@ std::string HDF5Object::ReadAttrStr(const std::string &name) {
H5Tclose(type_id);
H5Aclose(attr_id);
return {buffer.data()};
return ret;
}
@@ -923,22 +947,13 @@ std::string HDF5DataSet::ReadString() const {
}
HDF5DataType file_data_type(*this);
const size_t size = file_data_type.GetElemSize();
if (size == 0)
return "";
std::string buffer(size, '\0');
if (H5Dread(id, file_data_type.GetID(), H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer.data()) < 0)
throw JFJochException(JFJochExceptionCategory::HDF5,
dataset_name + ": read unsuccessful");
const size_t end = buffer.find('\0');
if (end != std::string::npos)
buffer.resize(end);
return buffer;
try {
return ReadHDF5String(file_data_type.GetID(), [&](hid_t type, void *buffer) {
return H5Dread(id, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer);
});
} catch (const JFJochException &) {
throw JFJochException(JFJochExceptionCategory::HDF5, dataset_name + ": read unsuccessful");
}
}
void HDF5DataSet::Close() {