refactor(776-2): 엑셀 한 번 열기(격자 = 읽은 책) · 줄 갈래 한 규칙 · EST 블록 금액 · 항목 · 옛 판 HP 409 「다시 올림」

- Excel — read_book 이 연 책(xls = formatting_info · 목록 시트까지)을 Grid.read 가 이어 씀(opened) · 거창 여는 횟수 2 → 1 · 여덟 파일 올림 시간 17.8 → 7.8 초
- Excel.kind_of = 두 형식 · 붙여넣기 한 규칙(STmate 코드 머리 · EST 칸 · 끝에 노임 올림) · Stmate._kind 걷음
- Excel — EST 블록에 금액 · 항목(ReadEst) 실음 · Basis 대신 ReadEst 직접
- Grid — 읽은 책 그대로 · xlsx 열 폭은 보기 시트 머리 <cols> 만(sheetData 에서 멈춤) · 여덟 파일 격자 결과 전과 같음
- Store.read 다른 판 = 409 「다시 올림」 · Forget.finish 는 옛 판도 끝냄(old=True)
- S1 · S2 해시 그대로 · test_718_1 옛 판 열기 409 · 끝내기 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 12:09:24 +09:00
co-authored by Claude Opus 5.5
parent a363bbc63a
commit 3a1ce9ca4f
6 changed files with 106 additions and 39 deletions
+19 -14
View File
@@ -24,13 +24,14 @@ import zipfile
from typing import Any
from openpyxl.utils import get_column_letter
from openpyxl.xml.functions import fromstring
from openpyxl.xml.functions import fromstring, iterparse
from M02_MasterTemplete import M02_Hopyo_Basis as hopyo_basis
from M02_MasterTemplete import M02_Hopyo_Excel as hopyo_excel
from M02_MasterTemplete import M02_Hopyo_ReadEst as hopyo_basis # EST 단가산출 시트 이름
from M02_MasterTemplete import M02_Hopyo_Stmate as hopyo_stmate
LIST_E, LIST_S, HOPYO_S = "단가산출총괄표", hopyo_stmate.LISTS[0], "일위대가목록표"
LIST_E, HOPYO_S = hopyo_excel.LISTS
LIST_S = hopyo_stmate.LISTS[0]
SHEETS = (
hopyo_excel.SHEET,
LIST_E,
@@ -185,10 +186,8 @@ def _xls_sheet(book: Any, sh: Any, styles: _Styles, sid: str) -> dict[str, Any]:
return out
def _xls(data: bytes) -> dict[str, Any]:
import xlrd
book = xlrd.open_workbook(file_contents=data, formatting_info=True, logfile=io.StringIO())
def _xls(book: Any) -> dict[str, Any]:
"""xlrd 책(`formatting_info` · 읽기가 연 것) → 격자."""
styles = _Styles()
found = {sh.name.strip(): sh for sh in book.sheets()}
sheets = [
@@ -203,8 +202,11 @@ def _xls(data: bytes) -> dict[str, Any]:
}
def _xlsx_widths(data: bytes) -> dict[str, dict[str, dict[str, Any]]]:
"""시트 이름 → 열 글자 → `{폭, 숨김}` — 시트 XML `<cols>` 를 읽음(read_only 는 폭을 안 줌)."""
def _xlsx_widths(
data: bytes, names: tuple[str, ...] | None = None
) -> dict[str, dict[str, dict[str, Any]]]:
"""시트 이름 → 열 글자 → `{폭, 숨김}` — 시트 XML 머리 `<cols>` 만 읽음(`<sheetData>` 에서 멈춤 ·
read_only 는 폭을 안 줌) · `names` = 이 시트만."""
out: dict[str, dict[str, dict[str, Any]]] = {}
try:
with zipfile.ZipFile(io.BytesIO(data)) as zf:
@@ -216,11 +218,15 @@ def _xlsx_widths(data: bytes) -> dict[str, dict[str, dict[str, Any]]]:
for sheet in fromstring(zf.read("xl/workbook.xml")).iter():
if tag(sheet) != "sheet":
continue
if names is not None and sheet.get("name", "").strip() not in names:
continue
rid = next((v for k, v in sheet.attrib.items() if k.endswith("}id")), None)
path = (rels.get(rid) or "").lstrip("/")
path = path if path.startswith("xl/") else f"xl/{path}"
cols: dict[str, dict[str, Any]] = {}
for col in fromstring(zf.read(path)).iter():
for _, col in iterparse(zf.open(path), events=("start",)):
if tag(col) == "sheetData":
break
if tag(col) != "col":
continue
first, last = int(col.get("min", 0)), min(int(col.get("max", 0)), 200)
@@ -240,7 +246,7 @@ def _xlsx(
) -> dict[str, Any]:
# ponytail: xlsx 는 값 · 열 폭만(병합 · 서식은 openpyxl 비 read_only 로 무거움) — 필요하면 그때
found = found or hopyo_excel._sheets(name, data, EXTRA)
widths = _xlsx_widths(data)
widths = _xlsx_widths(data, SHEETS)
sheets = []
for at, title in enumerate((n for n in SHEETS if n in found), 1):
rows = found[title]
@@ -319,9 +325,8 @@ def _lists(rows: dict[str, Any], parsed: dict[str, Any]) -> dict[str, list[dict[
def read(name: str, data: bytes, parsed: dict[str, Any]) -> dict[str, Any]:
"""엑셀 → `{책: Workbook, 호표, 단산, 보기, 원본, 목록, 줄}` — `parsed` =
`M02_Hopyo_Excel.read_book` 결과(행 범위)."""
xlsx = data[:4] == b"PK\x03\x04" or name.lower().endswith(".xlsx")
rows = hopyo_excel._sheets(name, data, EXTRA) # ponytail: xls 는 서식 없이 한 번 더 읽음
book = _xlsx(name, data, rows) if xlsx else _xls(data)
rows, xls = hopyo_excel.opened(name, data, EXTRA) # 읽기가 연 책 그대로(한 번 열기 · 776-2)
book = _xlsx(name, data, rows) if xls is None else _xls(xls)
keep = ("번", "이름", "행")
ids = {s["이름"]: s["id"] for s in book["시트"]}
on = {role: next((n for n in names if n in ids), None) for role, names in ROLES.items()}