Files
_Geulbeot/03. Code/geulbeot_10th/converters/hwpx_table_injector.py

174 lines
5.5 KiB
Python

# -*- coding: utf-8 -*-
"""
HWPX 표 열 너비 수정기 v2
표 생성 후 HWPX 파일을 직접 수정하여 열 너비 적용
"""
import zipfile
import re
from pathlib import Path
import tempfile
import shutil
# mm → HWPML 단위 변환 (1mm ≈ 283.46 HWPML units)
MM_TO_HWPML = 7200 / 25.4 # ≈ 283.46
def inject_table_widths(hwpx_path: str, table_widths_list: list):
"""
HWPX 파일의 표 열 너비를 수정
Args:
hwpx_path: HWPX 파일 경로
table_widths_list: [[w1, w2, w3], [w1, w2], ...] 형태 (mm 단위)
"""
if not table_widths_list:
print(" [INFO] 수정할 표 없음")
return
print(f"📐 HWPX 표 열 너비 수정 시작... ({len(table_widths_list)}개 표)")
# HWPX 압축 해제
temp_dir = Path(tempfile.mkdtemp(prefix="hwpx_table_"))
with zipfile.ZipFile(hwpx_path, 'r') as zf:
zf.extractall(temp_dir)
# section*.xml 파일들에서 표 찾기
contents_dir = temp_dir / "Contents"
table_idx = 0
total_modified = 0
for section_file in sorted(contents_dir.glob("section*.xml")):
with open(section_file, 'r', encoding='utf-8') as f:
content = f.read()
original_content = content
# 모든 표(<hp:tbl>...</hp:tbl>) 찾기
tbl_pattern = re.compile(r'(<hp:tbl\b[^>]*>)(.*?)(</hp:tbl>)', re.DOTALL)
def process_table(match):
nonlocal table_idx, total_modified
if table_idx >= len(table_widths_list):
return match.group(0)
tbl_open = match.group(1)
tbl_content = match.group(2)
tbl_close = match.group(3)
col_widths_mm = table_widths_list[table_idx]
col_widths_hwpml = [int(w * MM_TO_HWPML) for w in col_widths_mm]
# 표 전체 너비 수정 (hp:sz width="...")
total_width = int(sum(col_widths_mm) * MM_TO_HWPML)
tbl_content = re.sub(
r'(<hp:sz\s+width=")(\d+)(")',
lambda m: f'{m.group(1)}{total_width}{m.group(3)}',
tbl_content,
count=1
)
# 각 셀의 cellSz width 수정
# 방법: colAddr별로 너비 매핑
def replace_cell_width(tc_match):
tc_content = tc_match.group(0)
# colAddr 추출
col_addr_match = re.search(r'<hp:cellAddr\s+colAddr="(\d+)"', tc_content)
if not col_addr_match:
return tc_content
col_idx = int(col_addr_match.group(1))
if col_idx >= len(col_widths_hwpml):
return tc_content
new_width = col_widths_hwpml[col_idx]
# cellSz width 교체
tc_content = re.sub(
r'(<hp:cellSz\s+width=")(\d+)(")',
lambda m: f'{m.group(1)}{new_width}{m.group(3)}',
tc_content
)
return tc_content
# 각 <hp:tc>...</hp:tc> 블록 처리
tbl_content = re.sub(
r'<hp:tc\b[^>]*>.*?</hp:tc>',
replace_cell_width,
tbl_content,
flags=re.DOTALL
)
print(f" ✅ 표 #{table_idx + 1}: {col_widths_mm} mm → HWPML 적용")
table_idx += 1
total_modified += 1
return tbl_open + tbl_content + tbl_close
# 표 처리
new_content = tbl_pattern.sub(process_table, content)
# 변경사항 있으면 저장
if new_content != original_content:
with open(section_file, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"{section_file.name} 저장됨")
# 다시 압축
repack_hwpx(temp_dir, hwpx_path)
# 임시 폴더 삭제
shutil.rmtree(temp_dir)
print(f" ✅ 총 {total_modified}개 표 열 너비 수정 완료")
def repack_hwpx(source_dir: Path, output_path: str):
"""HWPX 파일 다시 압축"""
import os
import time
temp_output = output_path + ".tmp"
with zipfile.ZipFile(temp_output, 'w', zipfile.ZIP_DEFLATED) as zf:
# mimetype은 압축 없이 첫 번째로
mimetype_path = source_dir / "mimetype"
if mimetype_path.exists():
zf.write(mimetype_path, "mimetype", compress_type=zipfile.ZIP_STORED)
# 나머지 파일들
for root, dirs, files in os.walk(source_dir):
for file in files:
if file == "mimetype":
continue
file_path = Path(root) / file
arcname = file_path.relative_to(source_dir)
zf.write(file_path, arcname)
# 원본 교체
for attempt in range(3):
try:
if os.path.exists(output_path):
os.remove(output_path)
os.rename(temp_output, output_path)
break
except PermissionError:
time.sleep(0.5)
# 테스트용
if __name__ == "__main__":
test_widths = [
[18.2, 38.9, 42.8, 70.1],
[19.9, 79.6, 70.5],
[28.7, 81.4, 59.9],
[19.2, 61.4, 89.5],
]
hwpx_path = r"C:\Users\User\AppData\Local\Temp\geulbeot_output.hwpx"
inject_table_widths(hwpx_path, test_widths)