Files
test/handlers/doc_template_analyzer.py

150 lines
5.1 KiB
Python

# -*- coding: utf-8 -*-
"""
문서 템플릿 분석기 v5.1 (오케스트레이터)
역할: tools/ 모듈을 조합하여 HWPX → 템플릿 정보 추출
- 직접 파싱 로직 없음 (모두 tools에 위임)
- 디폴트값 생성 없음 (tools가 None 반환하면 결과에서 제외)
- 사용자 추가 사항(config.json) → 템플릿에도 반영
구조:
tools/
page_setup.py §7 용지/여백
font.py §3 글꼴
char_style.py §4 글자 모양
para_style.py §5 문단 모양
border_fill.py §2 테두리/배경
table.py §6 표
header_footer.py §8 머리말/꼬리말
section.py §9 구역 정의
style_def.py 스타일 정의
numbering.py 번호매기기/글머리표
image.py 이미지
"""
import json
from pathlib import Path
from typing import Optional
from .tools import (
page_setup,
font,
char_style,
para_style,
border_fill,
table,
header_footer,
section,
style_def,
numbering,
image,
content_order,
)
class DocTemplateAnalyzer:
"""HWPX → 템플릿 추출 오케스트레이터"""
# ================================================================
# Phase 1: 추출 (모든 tools 호출)
# ================================================================
def analyze(self, parsed: dict) -> dict:
"""HWPX parsed 결과에서 템플릿 구조 추출.
Args:
parsed: processor.py가 HWPX를 파싱한 결과 dict.
raw_xml, section_xml, header_xml, footer_xml,
tables, paragraphs 등 포함.
Returns:
추출된 항목만 포함하는 dict (None인 항목은 제외).
"""
raw_xml = parsed.get("raw_xml", {})
extractors = {
"page": lambda: page_setup.extract(raw_xml, parsed),
"fonts": lambda: font.extract(raw_xml, parsed),
"char_styles": lambda: char_style.extract(raw_xml, parsed),
"para_styles": lambda: para_style.extract(raw_xml, parsed),
"border_fills": lambda: border_fill.extract(raw_xml, parsed),
"tables": lambda: table.extract(raw_xml, parsed),
"header": lambda: header_footer.extract_header(raw_xml, parsed),
"footer": lambda: header_footer.extract_footer(raw_xml, parsed),
"section": lambda: section.extract(raw_xml, parsed),
"styles": lambda: style_def.extract(raw_xml, parsed),
"numbering": lambda: numbering.extract(raw_xml, parsed),
"images": lambda: image.extract(raw_xml, parsed),
"content_order":lambda: content_order.extract(raw_xml, parsed),
}
result = {}
for key, extractor in extractors.items():
try:
value = extractor()
if value is not None:
result[key] = value
except Exception as e:
# 개별 tool 실패 시 로그만, 전체 중단 안 함
result.setdefault("_errors", []).append(
f"{key}: {type(e).__name__}: {e}"
)
return result
# ================================================================
# Phase 2: 사용자 추가 사항 병합
# ================================================================
def merge_user_config(self, template_info: dict,
config: dict) -> dict:
"""config.json의 사용자 요구사항을 template_info에 병합.
사용자가 문서 유형 추가 시 지정한 커스텀 사항을 반영:
- 색상 오버라이드
- 글꼴 오버라이드
- 제목 크기 오버라이드
- 기타 레이아웃 커스텀
이 병합 결과는 style.json에 저장되고,
이후 template.html 생성 시에도 반영됨.
Args:
template_info: analyze()의 결과
config: config.json 내용
Returns:
병합된 template_info (원본 수정됨)
"""
user_overrides = config.get("user_overrides", {})
if not user_overrides:
return template_info
# 모든 사용자 오버라이드를 template_info에 기록
template_info["user_overrides"] = user_overrides
return template_info
# ================================================================
# Phase 3: template_info → style.json 저장
# ================================================================
def save_style(self, template_info: dict,
save_path: Path) -> Path:
"""template_info를 style.json으로 저장.
Args:
template_info: analyze() + merge_user_config() 결과
save_path: 저장 경로 (예: templates/user/{doc_type}/style.json)
Returns:
저장된 파일 경로
"""
save_path = Path(save_path)
save_path.parent.mkdir(parents=True, exist_ok=True)
with open(save_path, 'w', encoding='utf-8') as f:
json.dump(template_info, f, ensure_ascii=False, indent=2)
return save_path