Files
Aislo/tools/stmate-test/stc_codechg.py
T

88 lines
3.6 KiB
Python

# -*- coding: utf-8 -*-
"""코드 일괄 변경(ST_U_ChgCode Chg_Code_Sel · Exec_Change_Code) — 옛 코드 → 새 코드를 프로젝트 전체에 바꿈.
근거: 10_System_186개_파일분석장부.md(ST_U_ChgCode — 코드 일괄·선택 변경 · Excel 왕복 · CH_CODE 임시표) ·
23번(COSTN.SMT 식 참조 &코드[LMS]&) · STC_난독화_상태.md(BDQTY 자리별 변환) · 02번(BOQ BDCODE1~4)
바꾸는 자리: COSTN.CODE · BOQ1n.BDCODE1~5 · BDQTY.BDCODE·CODE(암호 그대로 다시 숨김) · COSTN.SMT 식의 &코드…& ·
QCODE.BQCODE · BOQ1n.CODEA
규칙: 새 코드는 6자 영숫자 · 이미 있는 코드로는 못 바꿈 · 같은 길이라 메모는 제자리 덮어씀
"""
from __future__ import annotations
import re
from dbf_crypt import BdqtyCodec
from dbf_memo import SixMemo
from dbf_rw import DbfTable
from stc_save import SaveLog
CODE_RE = re.compile(r"^[A-Z][0-9A-Z]{5}$")
def _check(files: dict, mapping: dict[str, str]) -> dict[str, str]:
ct = DbfTable(files["COSTN.DBF"])
have = {ct.get(i, "CODE") for i in range(len(ct))}
m = {
str(a).strip().upper(): str(b).strip().upper() for a, b in mapping.items() if str(b).strip()
}
m = {a: b for a, b in m.items() if a != b}
for a, b in m.items():
if a not in have:
raise ValueError(f"COSTN 에 없는 코드: {a}")
if not CODE_RE.match(b):
raise ValueError(f"새 코드 형식 오류(영문 1자 + 5자): {b}")
if b in have and b not in m:
raise ValueError(f"이미 있는 코드로 바꿀 수 없음: {a} → {b}")
if len(set(m.values())) != len(m):
raise ValueError("새 코드가 겹침")
return m
def _table(
files: dict, out: dict, name: str, cols: tuple[str, ...], m: dict, log: SaveLog, codec=None
):
if name not in files:
return
t = DbfTable(files[name], codec=codec)
for i in range(len(t)):
for c in cols:
if c in t.fields:
v = t.get(i, c)
if v in m and t.set_raw(i, c, t.encode_value(c, m[v])):
log.add(name, i, c, v, m[v])
if t.dirty:
out[name] = t.to_bytes()
def change_codes(files: dict, mapping: dict[str, str]) -> tuple[dict, list]:
"""{옛: 새} → 새 files · 바뀐 자리 목록."""
m = _check(files, mapping)
out, log = dict(files), SaveLog()
if not m:
return out, []
_table(files, out, "COSTN.DBF", ("CODE",), m, log)
for name in sorted(k for k in files if re.match(r"^BOQ\d*\.DBF$", k)):
_table(files, out, name, tuple(f"BDCODE{n}" for n in range(1, 6)) + ("CODEA",), m, log)
_table(files, out, "BDQTY.DBF", ("BDCODE", "CODE"), m, log, codec=BdqtyCodec())
_table(files, out, "QCODE.DBF", ("BQCODE",), m, log)
if "COSTN.SMT" in files and "IFORM" in DbfTable(out["COSTN.DBF"]).fields:
ct = DbfTable(out["COSTN.DBF"])
memo = SixMemo(files["COSTN.SMT"])
pat = re.compile(r"&(" + "|".join(map(re.escape, m)) + r")([LMS]?)&")
for i in range(len(ct)):
ptr = ct.raw(i, "IFORM")
body = memo.read(ptr)
if not body:
continue
text = body.decode("cp949", "replace")
new = pat.sub(lambda x: f"&{m[x.group(1)]}{x.group(2)}&", text)
if new != text:
new_ptr = memo.write(ptr, new.encode("cp949"))
if ct.set_raw(i, "IFORM", new_ptr):
out["COSTN.DBF"] = ct.to_bytes()
log.add("COSTN.SMT", i, ct.get(i, "CODE"), "", f"{len(pat.findall(text))}곳")
if memo.dirty:
out["COSTN.SMT"] = memo.to_bytes()
return out, log.items