166 lines
8.0 KiB
Python
166 lines
8.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""구버전 → Win8 설정 — 판 가리기(Chk_STDir) · 7.x Start.Ini → Start8x.Ini · DOS Start.MEM 읽기.
|
|
|
|
근거(지식DB REA 분석/37 §2 · ST_U_Archive Chk_Data 0x459280 · Chk_STDir 0x45a78c · ST_U_Convert 0x48e07c · 0x48fea8):
|
|
판 = 파일 이름 묶음 규칙(위에서 처음 맞는 것) · 120·130·140 은 그 설정 [PROJECT] PMODE(기본 1)를 더함 · 110 은 111 고정
|
|
7.x 설정 키 대응은 0x48e07c ReadString/Write 차례 · DOS MEM = Clipper 변수 머리 32바이트(이름 11 · 형 +11 · 길이 +16·+17)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import struct
|
|
|
|
from dbf_memo import ini_items
|
|
|
|
RULES = [
|
|
(140, ("STINI.MEM", "RATE.DBF", "QCODE.DBF")),
|
|
(130, ("START8X.INI", "RATE.DBF", "QCODE.DBF")),
|
|
(120, ("START.INI", "FORM.DBF", "KANJUB2.DBF", "IMG.DB")),
|
|
(110, ("START.MEM", "FORM.DBF", "KANJUB2.DBF", "COSTN1.DBF")),
|
|
(150, ("START8X", "COSTA.DBF", "COSTN.SMT", "PRJLST.SMT", "SYSINFO.DBF")),
|
|
]
|
|
JODAL = ("GLOBAL.INI", "M.TXT", "L.TXT", "E.TXT", "W.TXT", "UNIT.TXT", "UNITPART.TXT", "J.TXT", "JPART.TXT")
|
|
JODAL += ("JUNIT.TXT", "DANGA.TXT", "DANGA.DBF", "DANGA.DBT", "GONGJONG.TXT", "MAIN.TXT", "MAINPART.TXT")
|
|
RULES.append((200, JODAL))
|
|
CORE = ("COSTN.DBF", "BDQTY.DBF", "PRJLST.DBF", "GRASS.DBF")
|
|
|
|
|
|
def ini_dict(text: str) -> dict[str, dict[str, str]]:
|
|
out: dict[str, dict[str, str]] = {}
|
|
for sec, k, v in ini_items(text):
|
|
out.setdefault(sec.upper(), {}).setdefault(k.upper(), v)
|
|
return out
|
|
|
|
|
|
def _pmode(files: dict, name: str) -> int:
|
|
raw = files.get(name)
|
|
if raw is None:
|
|
return 1
|
|
v = ini_dict(raw.decode("cp949", "replace")).get("PROJECT", {}).get("PMODE", "1")
|
|
try:
|
|
return int(v.strip() or "1")
|
|
except ValueError:
|
|
return 0
|
|
|
|
|
|
def detect(files: dict) -> int:
|
|
"""폴더 파일 이름(대문자) → 판 번호 — 111 DOS · 120+PMODE 7.x · 130/140+PMODE 이미 Win8 · 200 조달청 TEXT."""
|
|
names = {n.upper() for n in files}
|
|
code = 0
|
|
if len(":".join(names)) >= 5:
|
|
code = next((c for c, need in RULES if all(n in names for n in need)), 0)
|
|
if code == 0:
|
|
return 100 if names & {"COSTN.DBF", "BDQTY.DBF", "PRJLST.DBF"} else 0
|
|
if code not in (150, 200) and not all(n in names for n in CORE):
|
|
return 100
|
|
if code == 110:
|
|
return 111
|
|
ini = {120: "START.INI", 130: "START8X.INI", 140: "STINI.MEM"}.get(code)
|
|
return code + _pmode(files, ini) if ini else code
|
|
|
|
|
|
# 7.x [START] 키 — (대상 키, 원본 키, 기본값)
|
|
START_7X = [("BMKNM", "BMKNM", "MLST"), ("ACDNM", "ACDNM", "1"), ("BPNNM", "ACDNM", "1")]
|
|
START_7X += [("KCDNM", "KCDNM", "1"), ("ZCDNM", "ZCDNM", "2"), ("CHGNM", "CHGNM", "1"), ("CHBNM", "CHGNM", "1")]
|
|
START_7X += [("CHNNM", "CHNNM", "1"), ("BCDNM", "BCDNM", "0"), ("DCDNM", "DCDNM", "0"), ("ICDNM", "ICDNM", "0")]
|
|
START_7X += [("LCDNM", "LCDNM", "1"), ("MCDNM", "MCDNM", "1"), ("PCDNM", "PCDNM", "10")]
|
|
START_7X += [("SCDNM", "SCDNM", "0"), ("WCDNM", "WCDNM", "0"), ("SUBNM", "SUBNM", ""), ("LMGNM", "LMGNM", "4")]
|
|
START_7X += [("MKRNM", "MKRNM", "5"), ("TMGNM", "TMGNM", "6"), ("JEMNM", "JEMNM", "123456")]
|
|
JMOK = ["가격 정보", "물가 자료", "유통 물가", "견적단가1", "견적단가2", "적용 단가"]
|
|
START_7X += [(f"JMOK{i}", f"JMOK{i}", v) for i, v in enumerate(JMOK, 1)]
|
|
START_7X += [(f"JMK{i}0", f"JMK{i}0", v) for i, v in enumerate(["가.정", "물.자", "유.물", "견적 1", "견적 2"], 1)]
|
|
START_7X += [("JUKNM", "JUKNM", "0")]
|
|
EQUIP_7X = [("RXXNM", "START", "RXXNM", "1/8 * 16/12 * 25/20"), ("RXTNM", "START", "RXTNM", "")]
|
|
EQUIP_7X += [("EXLNM1", "START", "EX1NM", "1000"), ("EXHNM1", "START", "EX2NM", "1000")]
|
|
EQUIP_7X += [("EXLNM2", "START", "EXCNM", "1000"), ("EXHNM2", "START", "EX0NM", "1000")]
|
|
EQUIP_7X += [(k, "START", "EX1NM", "1000") for k in ("EXLNM3", "EXHNM3", "EXLNM4", "EXHNM4")]
|
|
|
|
|
|
def write_ini(sections: list[tuple[str, list[tuple[str, str]]]]) -> bytes:
|
|
out = []
|
|
for sec, items in sections:
|
|
if items:
|
|
out.append(f"[{sec}]")
|
|
out += [f"{k}={v}" for k, v in items]
|
|
return ("\r\n".join(out) + "\r\n").encode("cp949", "replace")
|
|
|
|
|
|
def ini_from_7x(start_ini: bytes, conld: str, contd: str) -> bytes:
|
|
"""7.x Start.Ini → Start8x.Ini(0x48e07c) — 원본에 없는 키는 기본값."""
|
|
src = ini_dict(start_ini.decode("cp949", "replace"))
|
|
g = lambda sec, k, d: src.get(sec, {}).get(k, d) # noqa: E731
|
|
start = [("ARENM", "1" if g("START", "ARENM", "A->") == "A->" else "2")]
|
|
start += [(t, g("START", s, d)) for t, s, d in START_7X]
|
|
prt = [("ORDNM", g("PRINT", "ORDNM", "")), ("FONT", g("PRINT", "CBFONT", "굴림체")), ("F_COL0", "clBlack")]
|
|
prt += [("F_COL1", g("PRINT", "COLDAN", "clRed")), ("F_COL2", g("PRINT", "COLBYN", "clBlack"))]
|
|
prt += [("SH_TYP0", g("PRINT", "FOMNM", "0")), ("SAN_TYP", g("PRINT", "LANNM", "0"))]
|
|
prt += [("SAN_AMT", "1"), ("FLT_JAB", "3")]
|
|
eq = [(t, g(s, k, d)) for t, s, k, d in EQUIP_7X]
|
|
eq += [(k, v) for k, v in (("CONLD", conld), ("CONTD", contd)) if v]
|
|
prj = [("PCODE", "XXXX"), ("PMODE", g("PROJECT", "PMODE", "1")), ("PNAME", g("PROJECT", "PNAME", "프로젝트명"))]
|
|
prj.append(("P_DIR", ""))
|
|
nag = [("NAGNM", src["START"]["NAGNM"])] if "NAGNM" in src.get("START", {}) else []
|
|
for t, k in (("ST_MHAL", "PMHAL"), ("EQ_LHAL", "EQ_L"), ("EQ_MHAL", "EQ_M"), ("EQ_SHAL", "EQ_S")):
|
|
if k in src.get("PROJECT", {}):
|
|
nag.append((t, src["PROJECT"][k]))
|
|
secs = [("START", start), ("PRINT", prt), ("EQUIPMENT", eq), ("PROJECT", prj), ("NAG_HAL", nag)]
|
|
return write_ini(secs)
|
|
|
|
|
|
def read_mem(b: bytes) -> dict[str, object]:
|
|
"""Clipper .MEM 변수 — C 글(TrimRight) · N 실수(%-20.6F 뒤 Trim) · L 0/1 · D 율리우스 일수(조건부)."""
|
|
out: dict[str, object] = {}
|
|
o = 0
|
|
while o + 32 <= len(b) and b[o] not in (0x1A, 0):
|
|
name = b[o : o + 11].split(b"\x00", 1)[0].decode("ascii", "replace").upper()
|
|
typ = chr(b[o + 11] & 0x7F)
|
|
ln = b[o + 16] | (b[o + 17] << 8)
|
|
o += 32
|
|
if typ == "C":
|
|
out[name] = b[o : o + ln].split(b"\x00", 1)[0].decode("cp949", "replace").rstrip()
|
|
o += ln
|
|
elif typ in ("N", "D"):
|
|
v = struct.unpack_from("<d", b, o)[0]
|
|
out[name] = (f"{v:<20.6f}").strip() if typ == "N" else v
|
|
o += 8
|
|
elif typ == "L":
|
|
out[name] = "1" if b[o] else "0"
|
|
o += 1
|
|
else:
|
|
break
|
|
return out
|
|
|
|
|
|
DOS_PRINT = ("ILWNM", "TOGNM", "LANNM")
|
|
DOS_EQUIP = ("RXTNM", "RXXNM", "EX1NM", "EX2NM", "EXCNM", "EX0NM")
|
|
|
|
|
|
def ini_from_dos(mem: bytes, conld: str, contd: str) -> tuple[bytes, list[str]]:
|
|
"""DOS Start.MEM → Start8x.Ini(0x48fea8 → 0x44d370) — [START] 34키 표는 실행 중 채워져 이름 미확인 →
|
|
[PRINT]·[EQUIPMENT] 로 가는 키 밖의 MEM 변수는 [START] 로(조건부)."""
|
|
v = read_mem(mem)
|
|
start = [(k, str(x)) for k, x in v.items() if k not in DOS_PRINT + DOS_EQUIP]
|
|
start = [kv for kv in start if kv[0] not in ("SUBNM", "QCDNM", "XCDNM", "JUKNM")]
|
|
start += [("SUBNM", ""), ("QCDNM", "0"), ("XCDNM", "0"), ("JUKNM", "0")]
|
|
prt = [(k, str(v[k])) for k in DOS_PRINT if k in v]
|
|
prt += [("ORDNM", " "), ("FONT", "굴림체"), ("F_COL0", "clBlack"), ("F_COL1", "clRed"), ("F_COL2", "clBlack")]
|
|
eq = [(k, str(v[k])) for k in DOS_EQUIP if k in v]
|
|
eq += [(k, x) for k, x in (("CONLD", conld), ("CONTD", contd)) if x]
|
|
note = ["DOS [START] 키 이름 표(0x588294)는 실행 중 채워짐 — MEM 변수 이름 그대로 씀(조건부)"]
|
|
return write_ini([("START", start), ("PRINT", prt), ("EQUIPMENT", eq), ("PROJECT", [("PMODE", "1")])]), note
|
|
|
|
|
|
def b_list(rows: list[dict]) -> str:
|
|
"""CONLD/CONTD 의 BDCODE 중 B 로 시작하는 6자 코드를 겹치지 않게 이음(0x477e4c)."""
|
|
out = []
|
|
for r in rows:
|
|
c = str(r.get("BDCODE", "")).strip()
|
|
if c.startswith("B") and c not in out:
|
|
out.append(c)
|
|
return "".join(out)
|
|
|
|
|
|
def swap_codes(text: str, mapping: dict[str, str]) -> str:
|
|
return re.sub("|".join(map(re.escape, mapping)), lambda m: mapping[m.group(0)], text) if mapping else text
|