# -*- 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