- JDL 줄 = 탭 구분 + \x05/\x07 덧붙임(검사기 0x504d20 · 고정폭 표는 죽은 고리 — 34 §17.9 바로잡음) · 단가 목록 7종·Main·GongJong 머리·Global.Ini 상수 키 · zip 받기 - 구버전 판 가리기(Chk_Data 0x459280) · 7.x/DOS 표별 필드 대응 · B→X 조건 = 단위 X - 엑셀 역등록 PRPT「속성」·PRIN「대상」·PROUT「저장」 = PR/RR 줄만 · 원본 BDQTY 4건 일치 - 자재 = 재료비 수량집계표 엑셀 내보내기(주요건설자재현황 시트) - 그림 코드 = COSTN I 코드 IFORM · SIZE = 그림 클래스 이름 - 지식DB 37번 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
227 lines
8.3 KiB
Python
227 lines
8.3 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
|
|
)
|