57 lines
2.0 KiB
Python
57 lines
2.0 KiB
Python
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)
|