v4:코드모듈화_20260123
This commit is contained in:
@@ -13,6 +13,11 @@ from pyhwpx import Hwp
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
import os, re
|
||||
|
||||
# 스타일 그루핑 시스템 추가
|
||||
from converters.style_analyzer import StyleAnalyzer, StyledElement
|
||||
from converters.hwp_style_mapping import HwpStyleMapper, DEFAULT_STYLES, ROLE_TO_STYLE_NAME
|
||||
|
||||
|
||||
# PIL 선택적 import (이미지 크기 확인용)
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -25,9 +30,12 @@ class Config:
|
||||
MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM = 20, 20, 20, 15
|
||||
HEADER_LEN, FOOTER_LEN = 10, 10
|
||||
MAX_IMAGE_WIDTH = 150 # mm (최대 이미지 너비)
|
||||
ASSETS_PATH = r"D:\for python\geulbeot-light\geulbeot-light\output\assets" # 🆕 추가
|
||||
|
||||
class StyleParser:
|
||||
def __init__(self):
|
||||
self.style_map = {} # 스타일 매핑 (역할 → HwpStyle)
|
||||
self.sty_gen = None # 스타일 생성기
|
||||
self.class_styles = {
|
||||
'h1': {'font-size': '20pt', 'color': '#008000'},
|
||||
'h2': {'font-size': '16pt', 'color': '#03581d'},
|
||||
@@ -62,6 +70,34 @@ class StyleParser:
|
||||
|
||||
def is_bold(self, style): return style.get('font-weight', '') in ['bold', '700', '800', '900']
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 번호 제거 유틸리티
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
NUMBERING_PATTERNS = {
|
||||
'H1': re.compile(r'^(\d+)\.\s*'), # "1. " → ""
|
||||
'H2': re.compile(r'^(\d+)\.(\d+)\s*'), # "1.1 " → ""
|
||||
'H3': re.compile(r'^(\d+)\.(\d+)\.(\d+)\s*'), # "1.1.1 " → ""
|
||||
'H4': re.compile(r'^[가-하]\.\s*'), # "가. " → ""
|
||||
'H5': re.compile(r'^(\d+)\)\s*'), # "1) " → ""
|
||||
'H6': re.compile(r'^\((\d+)\)\s*'), # "(1) " → ""
|
||||
'H7': re.compile(r'^[①②③④⑤⑥⑦⑧⑨⑩]\s*'), # "① " → ""
|
||||
'LIST_ITEM': re.compile(r'^[•\-○]\s*'), # "• " → ""
|
||||
}
|
||||
|
||||
def strip_numbering(text: str, role: str) -> str:
|
||||
"""
|
||||
역할에 따라 텍스트 앞의 번호/기호 제거
|
||||
HWP 개요 기능이 번호를 자동 생성하므로 중복 방지
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
pattern = NUMBERING_PATTERNS.get(role)
|
||||
if pattern:
|
||||
return pattern.sub('', text).strip()
|
||||
|
||||
return text.strip()
|
||||
|
||||
class HtmlToHwpConverter:
|
||||
def __init__(self, visible=True):
|
||||
@@ -71,6 +107,8 @@ class HtmlToHwpConverter:
|
||||
self.base_path = ""
|
||||
self.is_first_h1 = True
|
||||
self.image_count = 0
|
||||
self.style_map = {} # 역할 → 스타일 이름 매핑
|
||||
self.sty_path = None # .sty 파일 경로
|
||||
|
||||
def _mm(self, mm): return self.hwp.MiliToHwpUnit(mm)
|
||||
def _pt(self, pt): return self.hwp.PointToHwpUnit(pt)
|
||||
@@ -155,6 +193,80 @@ class HtmlToHwpConverter:
|
||||
except Exception as e:
|
||||
print(f" [경고] 구역 머리말: {e}")
|
||||
|
||||
# 스타일 적용 관련 (🆕 NEW)
|
||||
|
||||
def _load_style_template(self, sty_path: str):
|
||||
"""
|
||||
.sty 스타일 템플릿 로드
|
||||
HWP에서 스타일 불러오기 기능 사용
|
||||
"""
|
||||
if not os.path.exists(sty_path):
|
||||
print(f" [경고] 스타일 파일 없음: {sty_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# HWP 스타일 불러오기
|
||||
self.hwp.HAction.GetDefault("StyleTemplate", self.hwp.HParameterSet.HStyleTemplate.HSet)
|
||||
self.hwp.HParameterSet.HStyleTemplate.filename = sty_path
|
||||
self.hwp.HAction.Execute("StyleTemplate", self.hwp.HParameterSet.HStyleTemplate.HSet)
|
||||
print(f" ✅ 스타일 템플릿 로드: {sty_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" [경고] 스타일 로드 실패: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _apply_style_by_name(self, style_name: str):
|
||||
"""
|
||||
현재 문단에 스타일 이름으로 적용
|
||||
텍스트 삽입 후 호출
|
||||
"""
|
||||
try:
|
||||
# 현재 문단 선택
|
||||
self.hwp.HAction.Run("MoveLineBegin")
|
||||
self.hwp.HAction.Run("MoveSelLineEnd")
|
||||
|
||||
# 스타일 적용
|
||||
self.hwp.HAction.GetDefault("Style", self.hwp.HParameterSet.HStyle.HSet)
|
||||
self.hwp.HParameterSet.HStyle.StyleName = style_name
|
||||
self.hwp.HAction.Execute("Style", self.hwp.HParameterSet.HStyle.HSet)
|
||||
|
||||
# 커서 문단 끝으로
|
||||
self.hwp.HAction.Run("MoveLineEnd")
|
||||
|
||||
except Exception as e:
|
||||
print(f" [경고] 스타일 적용 실패 '{style_name}': {e}")
|
||||
|
||||
|
||||
def _build_dynamic_style_map(self, elements: list):
|
||||
"""HTML 분석 결과 기반 동적 스타일 매핑 생성 (숫자)"""
|
||||
roles = set(elem.role for elem in elements)
|
||||
|
||||
# 제목 역할 정렬 (H1, H2, H3...)
|
||||
title_roles = sorted([r for r in roles if r.startswith('H') and r[1:].isdigit()],
|
||||
key=lambda x: int(x[1:]))
|
||||
|
||||
# 기타 역할
|
||||
other_roles = [r for r in roles if r not in title_roles]
|
||||
|
||||
# 순차 할당 (개요 1~10)
|
||||
self.style_map = {}
|
||||
style_num = 1
|
||||
|
||||
for role in title_roles:
|
||||
if style_num <= 10:
|
||||
self.style_map[role] = style_num
|
||||
style_num += 1
|
||||
|
||||
for role in other_roles:
|
||||
if style_num <= 10:
|
||||
self.style_map[role] = style_num
|
||||
style_num += 1
|
||||
|
||||
print(f" 📝 동적 스타일 매핑: {self.style_map}")
|
||||
return self.style_map
|
||||
|
||||
|
||||
|
||||
def _set_font(self, size=11, bold=False, color='#000000'):
|
||||
self.hwp.set_font(FaceName='맑은 고딕', Height=size, Bold=bold, TextColor=self._rgb(color))
|
||||
@@ -372,16 +484,22 @@ class HtmlToHwpConverter:
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
def _insert_image(self, src, caption=""):
|
||||
self.image_count += 1
|
||||
print(f" 📷 이미지 #{self.image_count}: {os.path.basename(src)}")
|
||||
|
||||
if not src:
|
||||
return
|
||||
|
||||
# 상대경로 → 절대경로
|
||||
if not os.path.isabs(src):
|
||||
full_path = os.path.normpath(os.path.join(self.base_path, src))
|
||||
else:
|
||||
full_path = src
|
||||
# 🆕 assets 폴더에서 먼저 찾기
|
||||
filename = os.path.basename(src)
|
||||
full_path = os.path.join(self.cfg.ASSETS_PATH, filename)
|
||||
|
||||
# assets에 없으면 기존 방식으로 fallback
|
||||
if not os.path.exists(full_path):
|
||||
if not os.path.isabs(src):
|
||||
full_path = os.path.normpath(os.path.join(self.base_path, src))
|
||||
else:
|
||||
full_path = src
|
||||
|
||||
print(f" 📷 이미지 #{self.image_count}: {filename}")
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
print(f" ❌ 파일 없음: {full_path}")
|
||||
@@ -450,7 +568,123 @@ class HtmlToHwpConverter:
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 오류: {e}")
|
||||
|
||||
|
||||
def _insert_table_from_element(self, elem: 'StyledElement'):
|
||||
"""StyledElement에서 표 삽입 (수정됨)"""
|
||||
table_data = elem.attributes.get('table_data', {})
|
||||
if not table_data:
|
||||
return
|
||||
|
||||
rows = table_data.get('rows', [])
|
||||
if not rows:
|
||||
return
|
||||
|
||||
num_rows = len(rows)
|
||||
num_cols = max(len(row) for row in rows) if rows else 1
|
||||
|
||||
print(f" → 표 삽입: {num_rows}행 × {num_cols}열")
|
||||
|
||||
try:
|
||||
# 1. 표 앞에 문단 설정
|
||||
self._set_para('left', 130, before=5, after=0)
|
||||
|
||||
# 2. 표 생성 (pyhwpx 내장 메서드 사용)
|
||||
self.hwp.create_table(num_rows, num_cols, treat_as_char=True)
|
||||
|
||||
# 3. 셀별 데이터 입력
|
||||
for row_idx, row in enumerate(rows):
|
||||
for col_idx, cell in enumerate(row):
|
||||
# 셀 건너뛰기 (병합된 셀)
|
||||
if col_idx >= len(row):
|
||||
self.hwp.HAction.Run("TableRightCell")
|
||||
continue
|
||||
|
||||
cell_text = cell.get('text', '')
|
||||
is_header = cell.get('is_header', False)
|
||||
|
||||
# 헤더 셀 스타일
|
||||
if is_header:
|
||||
self._set_cell_bg('#E8F5E9')
|
||||
self.hwp.HAction.Run("ParagraphShapeAlignCenter")
|
||||
self._set_font(9, True, '#006400')
|
||||
else:
|
||||
self._set_font(9.5, False, '#333333')
|
||||
|
||||
# 텍스트 입력
|
||||
self.hwp.insert_text(cell_text)
|
||||
|
||||
# 다음 셀로 (마지막 셀 제외)
|
||||
if not (row_idx == num_rows - 1 and col_idx == num_cols - 1):
|
||||
self.hwp.HAction.Run("TableRightCell")
|
||||
|
||||
# 4. ★ 표 빠져나오기 (핵심!)
|
||||
self.hwp.HAction.Run("Cancel") # 선택 해제
|
||||
self.hwp.HAction.Run("CloseEx") # 표 편집 종료
|
||||
self.hwp.HAction.Run("MoveDocEnd") # 문서 끝으로
|
||||
|
||||
# 5. 표 뒤 문단
|
||||
self._set_para('left', 130, before=5, after=5)
|
||||
self.hwp.BreakPara()
|
||||
|
||||
print(f" ✅ 표 삽입 완료")
|
||||
|
||||
except Exception as e:
|
||||
print(f" [오류] 표 삽입 실패: {e}")
|
||||
# 표 안에 갇혔을 경우 탈출 시도
|
||||
try:
|
||||
self.hwp.HAction.Run("Cancel")
|
||||
self.hwp.HAction.Run("CloseEx")
|
||||
self.hwp.HAction.Run("MoveDocEnd")
|
||||
except:
|
||||
pass
|
||||
|
||||
def _move_to_cell(self, row: int, col: int):
|
||||
"""표에서 특정 셀로 이동"""
|
||||
# 첫 셀로 이동
|
||||
self.hwp.HAction.Run("TableColBegin")
|
||||
self.hwp.HAction.Run("TableRowBegin")
|
||||
|
||||
# row만큼 아래로
|
||||
for _ in range(row):
|
||||
self.hwp.HAction.Run("TableLowerCell")
|
||||
|
||||
# col만큼 오른쪽으로
|
||||
for _ in range(col):
|
||||
self.hwp.HAction.Run("TableRightCell")
|
||||
|
||||
def _apply_cell_style(self, bold=False, bg_color=None, align='left'):
|
||||
"""현재 셀 스타일 적용"""
|
||||
# 글자 굵기
|
||||
if bold:
|
||||
self.hwp.HAction.Run("CharShapeBold")
|
||||
|
||||
# 정렬
|
||||
align_actions = {
|
||||
'left': "ParagraphShapeAlignLeft",
|
||||
'center': "ParagraphShapeAlignCenter",
|
||||
'right': "ParagraphShapeAlignRight",
|
||||
}
|
||||
if align in align_actions:
|
||||
self.hwp.HAction.Run(align_actions[align])
|
||||
|
||||
# 배경색
|
||||
if bg_color:
|
||||
self._apply_cell_bg(bg_color)
|
||||
|
||||
def _apply_cell_bg(self, color: str):
|
||||
"""셀 배경색 적용"""
|
||||
try:
|
||||
color = color.lstrip('#')
|
||||
r, g, b = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)
|
||||
|
||||
self.hwp.HAction.GetDefault("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
|
||||
self.hwp.HParameterSet.HCellBorderFill.FillAttr.FillType = 1 # 단색
|
||||
self.hwp.HParameterSet.HCellBorderFill.FillAttr.WinBrush.FaceColor = self.hwp.RGBColor(r, g, b)
|
||||
self.hwp.HAction.Execute("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
|
||||
except Exception as e:
|
||||
print(f" [경고] 셀 배경색: {e}")
|
||||
|
||||
|
||||
def _insert_highlight_box(self, elem):
|
||||
txt = elem.get_text(strip=True)
|
||||
if not txt: return
|
||||
@@ -551,19 +785,225 @@ class HtmlToHwpConverter:
|
||||
print(f"\n✅ 저장: {output_path}")
|
||||
print(f" 이미지: {self.image_count}개 처리")
|
||||
|
||||
def convert_with_styles(self, html_path, output_path, sty_path=None):
|
||||
"""
|
||||
스타일 그루핑이 적용된 HWP 변환
|
||||
|
||||
✅ 수정: 기존 convert() 로직 + 스타일 적용
|
||||
"""
|
||||
print("="*60)
|
||||
print("HTML → HWP 변환기 v11 (스타일 그루핑)")
|
||||
print("="*60)
|
||||
|
||||
self.base_path = os.path.dirname(os.path.abspath(html_path))
|
||||
self.is_first_h1 = True
|
||||
self.image_count = 0
|
||||
|
||||
# 1. HTML 파일 읽기
|
||||
with open(html_path, 'r', encoding='utf-8') as f:
|
||||
html_content = f.read()
|
||||
|
||||
# 2. 스타일 분석
|
||||
from converters.style_analyzer import StyleAnalyzer
|
||||
from converters.hwp_style_mapping import HwpStyGenerator
|
||||
|
||||
analyzer = StyleAnalyzer()
|
||||
elements = analyzer.analyze(html_content)
|
||||
html_styles = analyzer.extract_css_styles(html_content)
|
||||
|
||||
print(f"\n📊 분석 결과: {len(elements)}개 요소")
|
||||
for role, count in analyzer.get_role_summary().items():
|
||||
print(f" {role}: {count}")
|
||||
|
||||
# 3. 스타일 매핑 생성
|
||||
sty_gen = HwpStyGenerator()
|
||||
sty_gen.update_from_html(html_styles)
|
||||
self.style_map = sty_gen.apply_to_hwp(self.hwp) # Dict[str, HwpStyle]
|
||||
self.sty_gen = sty_gen # 나중에 사용
|
||||
|
||||
# 4. ★ 기존 convert() 로직 그대로 사용 ★
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
title_tag = soup.find('title')
|
||||
if title_tag:
|
||||
full_title = title_tag.get_text(strip=True)
|
||||
footer_title = full_title.split(':')[0].strip()
|
||||
else:
|
||||
footer_title = ""
|
||||
|
||||
self.hwp.FileNew()
|
||||
self._setup_page()
|
||||
self._create_footer(footer_title)
|
||||
|
||||
raw = soup.find(id='raw-container')
|
||||
if raw:
|
||||
cover = raw.find(id='box-cover')
|
||||
if cover:
|
||||
print(" → 표지")
|
||||
for ch in cover.children:
|
||||
self._process(ch)
|
||||
self.hwp.HAction.Run("BreakPage")
|
||||
|
||||
toc = raw.find(id='box-toc')
|
||||
if toc:
|
||||
print(" → 목차")
|
||||
self.is_first_h1 = True
|
||||
self._underline_box("목 차", 20, '#008000')
|
||||
self.hwp.BreakPara()
|
||||
self.hwp.BreakPara()
|
||||
self._insert_list(toc.find('ul') or toc)
|
||||
self.hwp.HAction.Run("BreakPage")
|
||||
|
||||
summary = raw.find(id='box-summary')
|
||||
if summary:
|
||||
print(" → 요약")
|
||||
self.is_first_h1 = True
|
||||
self._process(summary)
|
||||
self.hwp.HAction.Run("BreakPage")
|
||||
|
||||
content = raw.find(id='box-content')
|
||||
if content:
|
||||
print(" → 본문")
|
||||
self.is_first_h1 = True
|
||||
self._process(content)
|
||||
else:
|
||||
self._process(soup.find('body') or soup)
|
||||
|
||||
# 5. 저장
|
||||
self.hwp.SaveAs(output_path)
|
||||
print(f"\n✅ 저장: {output_path}")
|
||||
print(f" 이미지: {self.image_count}개 처리")
|
||||
|
||||
|
||||
def _insert_styled_element(self, elem: 'StyledElement'):
|
||||
"""스타일이 지정된 요소 삽입 (수정됨)"""
|
||||
role = elem.role
|
||||
text = elem.text
|
||||
|
||||
# ═══ 특수 요소 처리 ═══
|
||||
|
||||
# 그림
|
||||
if role == 'FIGURE':
|
||||
src = elem.attributes.get('src', '')
|
||||
if src:
|
||||
self._insert_image(src)
|
||||
return
|
||||
|
||||
# 표
|
||||
if role == 'TABLE':
|
||||
self._insert_table_from_element(elem)
|
||||
return
|
||||
|
||||
# 표 셀/캡션은 TABLE에서 처리
|
||||
if role in ['TH', 'TD']:
|
||||
return
|
||||
|
||||
# 빈 텍스트 스킵
|
||||
if not text:
|
||||
return
|
||||
|
||||
# ═══ 텍스트 요소 처리 ═══
|
||||
|
||||
# 번호 제거 (HWP 개요가 자동 생성하면)
|
||||
# clean_text = strip_numbering(text, role) # 필요시 활성화
|
||||
clean_text = text # 일단 원본 유지
|
||||
|
||||
# 1. 스타일 설정 가져오기
|
||||
style_config = self._get_style_config(role)
|
||||
|
||||
# 2. 문단 모양 먼저 적용
|
||||
self._set_para(
|
||||
align=style_config.get('align', 'justify'),
|
||||
lh=style_config.get('line_height', 160),
|
||||
left=style_config.get('indent_left', 0),
|
||||
indent=style_config.get('indent_first', 0),
|
||||
before=style_config.get('space_before', 0),
|
||||
after=style_config.get('space_after', 0)
|
||||
)
|
||||
|
||||
# 3. 글자 모양 적용
|
||||
self._set_font(
|
||||
size=style_config.get('font_size', 11),
|
||||
bold=style_config.get('bold', False),
|
||||
color=style_config.get('color', '#000000')
|
||||
)
|
||||
|
||||
# 4. 텍스트 삽입
|
||||
self.hwp.insert_text(clean_text)
|
||||
|
||||
# 5. 스타일 적용 (F6 목록에서 참조되도록)
|
||||
style_name = self.style_map.get(role)
|
||||
if style_name:
|
||||
try:
|
||||
self.hwp.HAction.Run("MoveLineBegin")
|
||||
self.hwp.HAction.Run("MoveSelLineEnd")
|
||||
self.hwp.HAction.GetDefault("Style", self.hwp.HParameterSet.HStyle.HSet)
|
||||
self.hwp.HParameterSet.HStyle.StyleName = style_name
|
||||
self.hwp.HAction.Execute("Style", self.hwp.HParameterSet.HStyle.HSet)
|
||||
self.hwp.HAction.Run("MoveLineEnd")
|
||||
except:
|
||||
pass # 스타일 없으면 무시
|
||||
|
||||
# 6. 줄바꿈
|
||||
self.hwp.BreakPara()
|
||||
|
||||
|
||||
def _get_style_config(self, role: str) -> dict:
|
||||
"""역할에 따른 스타일 설정 반환"""
|
||||
|
||||
STYLE_CONFIGS = {
|
||||
# 표지
|
||||
'COVER_TITLE': {'font_size': 32, 'bold': True, 'align': 'center', 'color': '#1a365d', 'space_before': 20, 'space_after': 10},
|
||||
'COVER_SUBTITLE': {'font_size': 18, 'bold': False, 'align': 'center', 'color': '#555555'},
|
||||
'COVER_INFO': {'font_size': 12, 'align': 'center', 'color': '#666666'},
|
||||
|
||||
# 목차
|
||||
'TOC_H1': {'font_size': 12, 'bold': True, 'indent_left': 0},
|
||||
'TOC_H2': {'font_size': 11, 'indent_left': 5},
|
||||
'TOC_H3': {'font_size': 10, 'indent_left': 10, 'color': '#666666'},
|
||||
|
||||
# 제목 계층
|
||||
'H1': {'font_size': 20, 'bold': True, 'align': 'left', 'color': '#008000', 'space_before': 15, 'space_after': 8},
|
||||
'H2': {'font_size': 16, 'bold': True, 'align': 'left', 'color': '#03581d', 'space_before': 12, 'space_after': 6},
|
||||
'H3': {'font_size': 13, 'bold': True, 'align': 'left', 'color': '#228B22', 'space_before': 10, 'space_after': 5},
|
||||
'H4': {'font_size': 12, 'bold': True, 'align': 'left', 'indent_left': 3, 'space_before': 8, 'space_after': 4},
|
||||
'H5': {'font_size': 11, 'bold': True, 'align': 'left', 'indent_left': 6, 'space_before': 6, 'space_after': 3},
|
||||
'H6': {'font_size': 11, 'bold': False, 'align': 'left', 'indent_left': 9},
|
||||
'H7': {'font_size': 10.5, 'bold': False, 'align': 'left', 'indent_left': 12},
|
||||
|
||||
# 본문
|
||||
'BODY': {'font_size': 11, 'align': 'justify', 'line_height': 180, 'indent_first': 3},
|
||||
'LIST_ITEM': {'font_size': 11, 'align': 'left', 'indent_left': 5},
|
||||
'HIGHLIGHT_BOX': {'font_size': 10.5, 'align': 'left', 'indent_left': 3},
|
||||
|
||||
# 표
|
||||
'TH': {'font_size': 9, 'bold': True, 'align': 'center', 'color': '#006400'},
|
||||
'TD': {'font_size': 9.5, 'align': 'left'},
|
||||
'TABLE_CAPTION': {'font_size': 10, 'bold': True, 'align': 'center'},
|
||||
|
||||
# 그림
|
||||
'FIGURE': {'align': 'center'},
|
||||
'FIGURE_CAPTION': {'font_size': 9.5, 'align': 'center', 'color': '#666666'},
|
||||
|
||||
# 기타
|
||||
'UNKNOWN': {'font_size': 11, 'align': 'left'},
|
||||
}
|
||||
|
||||
return STYLE_CONFIGS.get(role, STYLE_CONFIGS['UNKNOWN'])
|
||||
|
||||
def close(self):
|
||||
try: self.hwp.Quit()
|
||||
except: pass
|
||||
|
||||
|
||||
def main():
|
||||
html_path = r"D:\for python\survey_test\output\generated\report.html"
|
||||
output_path = r"D:\for python\survey_test\output\generated\report_v12.hwp"
|
||||
output_path = r"D:\for python\survey_test\output\generated\report_styled.hwp"
|
||||
sty_path = r"D:\for python\survey_test\교통영향평가스타일.sty" # 🆕 추가
|
||||
|
||||
try:
|
||||
conv = HtmlToHwpConverter(visible=True)
|
||||
conv.convert(html_path, output_path)
|
||||
input("\nEnter를 누르면 HWP가 닫힙니다...") # ← 선택사항
|
||||
conv.convert_with_styles(html_path, output_path, sty_path) # 🆕 sty_path 추가
|
||||
input("\nEnter를 누르면 HWP가 닫힙니다...")
|
||||
conv.close()
|
||||
except Exception as e:
|
||||
print(f"\n[에러] {e}")
|
||||
|
||||
Reference in New Issue
Block a user