// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "SFRM.h" #include #include #include #include #include #include #include #include "../common/JFJochException.h" namespace sfrm { namespace { constexpr size_t BLOCK = 512; constexpr size_t LINE = 80; constexpr size_t KEY = 8; // "NROWS :" - seven characters of name and the colon JFJochException Invalid(const std::string &what) { return {JFJochExceptionCategory::InputParameterInvalid, what}; } std::vector ReadPrefix(const std::string &path, size_t bytes) { std::ifstream f(path, std::ios::binary); if (!f) throw JFJochException(JFJochExceptionCategory::MockFileOpenError, "Cannot open " + path); std::vector out(bytes); f.read(reinterpret_cast(out.data()), static_cast(bytes)); out.resize(static_cast(f.gcount())); return out; } std::vector Words(const std::map &kv, const std::string &key) { std::vector out; const auto it = kv.find(key); if (it == kv.end()) return out; std::istringstream in(it->second); for (std::string w; in >> w;) out.push_back(w); return out; } double Num(const std::map &kv, const std::string &key, size_t i, double fallback = 0.0) { const auto w = Words(kv, key); if (i >= w.size()) return fallback; try { return std::stod(w[i]); } catch (const std::logic_error &) { throw Invalid("Malformed number in SFRM header " + key + ": '" + w[i] + "'"); } } int64_t Int(const std::map &kv, const std::string &key, size_t i, int64_t fallback = 0) { return static_cast(Num(kv, key, i, static_cast(fallback))); } // The 80-character lines of the header, as key -> value. A key that repeats (TITLE, CELL, MATRIX, // DETPAR, ...) continues its value on the next line, so the pieces are joined. std::map Lines(const char *data, size_t size) { std::map kv; for (size_t at = 0; at + LINE <= size; at += LINE) { if (data[at + KEY - 1] != ':') continue; // the padding after the last line std::string key(data + at, KEY - 1); key.erase(std::find_if(key.rbegin(), key.rend(), [](unsigned char c) { return !std::isspace(c); }).base(), key.end()); const std::string value(data + at + KEY, LINE - KEY); auto &v = kv[key]; v += (v.empty() ? "" : " ") + value; } return kv; } bool HasExtension(const std::filesystem::path &p) { std::string ext = p.extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); }); return ext == ".sfrm"; } bool IsSFRM(const std::string &path) { const auto head = ReadPrefix(path, KEY); return head.size() == KEY && std::memcmp(head.data(), "FORMAT :", KEY) == 0; } // "ins_zwin_01_0042.sfrm" -> {"ins_zwin_01_", 4}: the run is part of the prefix, so naming a frame // of run 01 reads run 01 and not the frames of every run in the directory. struct Template { std::string prefix; size_t digits = 0; bool Matches(const std::string &stem) const { if (stem.size() != prefix.size() + digits || stem.compare(0, prefix.size(), prefix) != 0) return false; return std::all_of(stem.begin() + static_cast(prefix.size()), stem.end(), [](unsigned char c) { return std::isdigit(c); }); } }; std::optional