import struct from pathlib import Path from data.field_tree import FieldTree class CobolBinaryReader: def read(self, binary_path: str, tree: FieldTree) -> list[dict]: data = Path(binary_path).read_bytes() record_size = self._compute_record_size(tree) if record_size == 0 or len(data) == 0: return [] records = [] for offset in range(0, len(data), record_size): record = data[offset:offset + record_size] if len(record) >= record_size: records.append(self._parse_record(record, tree)) return records def _compute_record_size(self, tree: FieldTree) -> int: max_end = 0 for f in tree.fields: end = f.offset + f.length if end > max_end: max_end = end return max_end def _parse_record(self, record: bytes, tree: FieldTree) -> dict: result = {} for name, field in tree.flatten().items(): if field.length == 0 or field.offset + field.length > len(record): continue raw = record[field.offset:field.offset + field.length] if field.usage == "COMP-3": result[name] = self._parse_comp3(raw, field.signed, field.decimal) elif field.usage == "COMP" or field.usage == "COMP-5": result[name] = int.from_bytes(raw, "big", signed=field.signed) else: result[name] = raw.decode("ascii", errors="replace").strip() return result def _parse_comp3(self, raw: bytes, signed: bool, decimal: int) -> str: if not raw: return "0" nibbles = [] for b in raw: nibbles.append((b >> 4) & 0x0F) nibbles.append(b & 0x0F) sign = nibbles.pop() value = 0 for n in nibbles: value = value * 10 + n if signed and sign in (0x0D, 0x0B): value = -value divisor = 10 ** decimal result = float(value) / divisor return f"{result:.{decimal}f}" if decimal else str(value)