344 lines
14 KiB
HTML
344 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>HWP 변환 가이드 - 글벗 Light</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
body { font-family: 'Noto Sans KR', sans-serif; }
|
|
.gradient-bg { background: linear-gradient(135deg, #1a365d 0%, #2c5282 100%); }
|
|
pre { background: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
|
|
code { font-family: 'Consolas', 'Monaco', monospace; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen">
|
|
<!-- 헤더 -->
|
|
<header class="gradient-bg text-white py-6 shadow-lg">
|
|
<div class="container mx-auto px-4">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<a href="/" class="text-blue-200 hover:text-white text-sm">← 메인으로</a>
|
|
<h1 class="text-2xl font-bold mt-2">HWP 변환 가이드</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container mx-auto px-4 py-8 max-w-4xl">
|
|
<!-- 안내 -->
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-xl p-6 mb-8">
|
|
<h2 class="font-bold text-yellow-800 mb-2">⚠️ HWP 변환 요구사항</h2>
|
|
<ul class="text-yellow-700 text-sm space-y-1">
|
|
<li>• Windows 운영체제</li>
|
|
<li>• 한글 프로그램 (한컴오피스) 설치</li>
|
|
<li>• Python 3.8 이상</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- 설치 방법 -->
|
|
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-4">1. 필요 라이브러리 설치</h2>
|
|
<pre><code>pip install pyhwpx beautifulsoup4</code></pre>
|
|
</div>
|
|
|
|
<!-- 사용 방법 -->
|
|
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-4">2. 사용 방법</h2>
|
|
<ol class="list-decimal list-inside space-y-2 text-gray-700">
|
|
<li>글벗 Light에서 HTML 파일을 다운로드합니다.</li>
|
|
<li>아래 Python 스크립트를 다운로드합니다.</li>
|
|
<li>스크립트 내 경로를 수정합니다.</li>
|
|
<li>스크립트를 실행합니다.</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<!-- 스크립트 -->
|
|
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-xl font-bold text-gray-800">3. HWP 변환 스크립트</h2>
|
|
<button onclick="copyScript()" class="px-4 py-2 bg-blue-900 text-white rounded hover:bg-blue-800 text-sm">
|
|
📋 복사
|
|
</button>
|
|
</div>
|
|
<pre id="scriptCode"><code># -*- coding: utf-8 -*-
|
|
"""
|
|
글벗 Light - HTML → HWP 변환기
|
|
Windows + 한글 프로그램 필요
|
|
"""
|
|
|
|
from pyhwpx import Hwp
|
|
from bs4 import BeautifulSoup
|
|
import os
|
|
|
|
|
|
class HtmlToHwpConverter:
|
|
def __init__(self, visible=True):
|
|
self.hwp = Hwp(visible=visible)
|
|
self.colors = {}
|
|
|
|
def _init_colors(self):
|
|
self.colors = {
|
|
'primary-navy': self.hwp.RGBColor(26, 54, 93),
|
|
'secondary-navy': self.hwp.RGBColor(44, 82, 130),
|
|
'dark-gray': self.hwp.RGBColor(45, 55, 72),
|
|
'medium-gray': self.hwp.RGBColor(74, 85, 104),
|
|
'bg-light': self.hwp.RGBColor(247, 250, 252),
|
|
'white': self.hwp.RGBColor(255, 255, 255),
|
|
'black': self.hwp.RGBColor(0, 0, 0),
|
|
}
|
|
|
|
def _mm(self, mm):
|
|
return self.hwp.MiliToHwpUnit(mm)
|
|
|
|
def _font(self, size=10, color='black', bold=False):
|
|
self.hwp.set_font(
|
|
FaceName='맑은 고딕',
|
|
Height=size,
|
|
Bold=bold,
|
|
TextColor=self.colors.get(color, self.colors['black'])
|
|
)
|
|
|
|
def _align(self, align):
|
|
actions = {'left': 'ParagraphShapeAlignLeft', 'center': 'ParagraphShapeAlignCenter', 'right': 'ParagraphShapeAlignRight'}
|
|
if align in actions:
|
|
self.hwp.HAction.Run(actions[align])
|
|
|
|
def _para(self, text='', size=10, color='black', bold=False, align='left'):
|
|
self._align(align)
|
|
self._font(size, color, bold)
|
|
if text:
|
|
self.hwp.insert_text(text)
|
|
self.hwp.BreakPara()
|
|
|
|
def _exit_table(self):
|
|
self.hwp.HAction.Run("Cancel")
|
|
self.hwp.HAction.Run("CloseEx")
|
|
self.hwp.HAction.Run("MoveDocEnd")
|
|
self.hwp.BreakPara()
|
|
|
|
def _set_cell_bg(self, color_name):
|
|
self.hwp.HAction.GetDefault("CellBorderFill", self.hwp.HParameterSet.HCellBorderFill.HSet)
|
|
pset = self.hwp.HParameterSet.HCellBorderFill
|
|
pset.FillAttr.type = self.hwp.BrushType("NullBrush|WinBrush")
|
|
pset.FillAttr.WinBrushFaceStyle = self.hwp.HatchStyle("None")
|
|
pset.FillAttr.WinBrushHatchColor = self.hwp.RGBColor(0, 0, 0)
|
|
pset.FillAttr.WinBrushFaceColor = self.colors.get(color_name, self.colors['white'])
|
|
pset.FillAttr.WindowsBrush = 1
|
|
self.hwp.HAction.Execute("CellBorderFill", pset.HSet)
|
|
|
|
def _create_header(self, left_text, right_text):
|
|
try:
|
|
self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
|
|
self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 0)
|
|
self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 0)
|
|
self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
|
|
self._font(9, 'medium-gray')
|
|
self.hwp.insert_text(left_text)
|
|
self.hwp.insert_text("\t" * 12)
|
|
self.hwp.insert_text(right_text)
|
|
self.hwp.HAction.Run("CloseEx")
|
|
except Exception as e:
|
|
print(f"머리말 생성 실패: {e}")
|
|
|
|
def _create_footer(self, text):
|
|
try:
|
|
self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
|
|
self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 0)
|
|
self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 1)
|
|
self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
|
|
self._align('center')
|
|
self._font(8.5, 'medium-gray')
|
|
self.hwp.insert_text(text)
|
|
self.hwp.HAction.Run("CloseEx")
|
|
except Exception as e:
|
|
print(f"꼬리말 생성 실패: {e}")
|
|
|
|
def _convert_lead_box(self, elem):
|
|
content = elem.find("div")
|
|
if not content:
|
|
return
|
|
text = ' '.join(content.get_text().split())
|
|
self.hwp.create_table(1, 1, treat_as_char=True)
|
|
self._set_cell_bg('bg-light')
|
|
self._font(11.5, 'dark-gray', False)
|
|
self.hwp.insert_text(text)
|
|
self._exit_table()
|
|
|
|
def _convert_bottom_box(self, elem):
|
|
left = elem.find(class_="bottom-left")
|
|
right = elem.find(class_="bottom-right")
|
|
if not left or not right:
|
|
return
|
|
left_text = ' '.join(left.get_text().split())
|
|
right_text = right.get_text(strip=True)
|
|
|
|
self.hwp.create_table(1, 2, treat_as_char=True)
|
|
self._set_cell_bg('primary-navy')
|
|
self._font(10.5, 'white', True)
|
|
self._align('center')
|
|
self.hwp.insert_text(left_text)
|
|
self.hwp.HAction.Run("MoveRight")
|
|
self._set_cell_bg('bg-light')
|
|
self._font(10.5, 'primary-navy', True)
|
|
self._align('center')
|
|
self.hwp.insert_text(right_text)
|
|
self._exit_table()
|
|
|
|
def _convert_section(self, section):
|
|
title = section.find(class_="section-title")
|
|
if title:
|
|
self._para("■ " + title.get_text(strip=True), 12, 'primary-navy', True)
|
|
|
|
ul = section.find("ul")
|
|
if ul:
|
|
for li in ul.find_all("li", recursive=False):
|
|
keyword = li.find(class_="keyword")
|
|
if keyword:
|
|
kw_text = keyword.get_text(strip=True)
|
|
full = li.get_text(strip=True)
|
|
rest = full.replace(kw_text, '', 1).strip()
|
|
self._font(10.5, 'primary-navy', True)
|
|
self.hwp.insert_text(" • " + kw_text + " ")
|
|
self._font(10.5, 'dark-gray', False)
|
|
self.hwp.insert_text(rest)
|
|
self.hwp.BreakPara()
|
|
else:
|
|
self._para(" • " + li.get_text(strip=True), 10.5, 'dark-gray')
|
|
self._para()
|
|
|
|
def _convert_sheet(self, sheet, is_first_page=False):
|
|
if is_first_page:
|
|
header = sheet.find(class_="page-header")
|
|
if header:
|
|
left = header.find(class_="header-left")
|
|
right = header.find(class_="header-right")
|
|
left_text = left.get_text(strip=True) if left else ""
|
|
right_text = right.get_text(strip=True) if right else ""
|
|
if left_text or right_text:
|
|
self._create_header(left_text, right_text)
|
|
|
|
footer = sheet.find(class_="page-footer")
|
|
if footer:
|
|
self._create_footer(footer.get_text(strip=True))
|
|
|
|
title = sheet.find(class_="header-title")
|
|
if title:
|
|
title_text = title.get_text(strip=True)
|
|
if '[첨부]' in title_text:
|
|
self._para(title_text, 15, 'primary-navy', True, 'left')
|
|
else:
|
|
self._para(title_text, 23, 'primary-navy', True, 'center')
|
|
self._font(10, 'secondary-navy')
|
|
self._align('center')
|
|
self.hwp.insert_text("━" * 45)
|
|
self.hwp.BreakPara()
|
|
|
|
self._para()
|
|
|
|
lead_box = sheet.find(class_="lead-box")
|
|
if lead_box:
|
|
self._convert_lead_box(lead_box)
|
|
self._para()
|
|
|
|
for section in sheet.find_all(class_="section"):
|
|
self._convert_section(section)
|
|
|
|
bottom_box = sheet.find(class_="bottom-box")
|
|
if bottom_box:
|
|
self._para()
|
|
self._convert_bottom_box(bottom_box)
|
|
|
|
def convert(self, html_path, output_path):
|
|
print(f"[입력] {html_path}")
|
|
|
|
with open(html_path, 'r', encoding='utf-8') as f:
|
|
soup = BeautifulSoup(f.read(), 'html.parser')
|
|
|
|
self.hwp.FileNew()
|
|
self._init_colors()
|
|
|
|
# 페이지 설정
|
|
try:
|
|
self.hwp.HAction.GetDefault("PageSetup", self.hwp.HParameterSet.HSecDef.HSet)
|
|
sec = self.hwp.HParameterSet.HSecDef
|
|
sec.PageDef.LeftMargin = self._mm(20)
|
|
sec.PageDef.RightMargin = self._mm(20)
|
|
sec.PageDef.TopMargin = self._mm(20)
|
|
sec.PageDef.BottomMargin = self._mm(20)
|
|
sec.PageDef.HeaderLen = self._mm(10)
|
|
sec.PageDef.FooterLen = self._mm(10)
|
|
self.hwp.HAction.Execute("PageSetup", sec.HSet)
|
|
except Exception as e:
|
|
print(f"페이지 설정 실패: {e}")
|
|
|
|
sheets = soup.find_all(class_="sheet")
|
|
total = len(sheets)
|
|
print(f"[변환] 총 {total} 페이지")
|
|
|
|
for i, sheet in enumerate(sheets, 1):
|
|
print(f"[{i}/{total}] 페이지 처리 중...")
|
|
self._convert_sheet(sheet, is_first_page=(i == 1))
|
|
if i < total:
|
|
self.hwp.HAction.Run("BreakPage")
|
|
|
|
self.hwp.SaveAs(output_path)
|
|
print(f"✅ 저장 완료: {output_path}")
|
|
|
|
def close(self):
|
|
try:
|
|
self.hwp.Quit()
|
|
except:
|
|
pass
|
|
|
|
|
|
def main():
|
|
# ====================================
|
|
# 경로 설정 (본인 환경에 맞게 수정)
|
|
# ====================================
|
|
html_path = r"C:\Users\User\Downloads\report.html"
|
|
output_path = r"C:\Users\User\Downloads\report.hwp"
|
|
|
|
print("=" * 50)
|
|
print("글벗 Light - HTML → HWP 변환기")
|
|
print("=" * 50)
|
|
|
|
try:
|
|
converter = HtmlToHwpConverter(visible=True)
|
|
converter.convert(html_path, output_path)
|
|
print("\n✅ 변환 완료!")
|
|
input("Enter를 누르면 HWP가 닫힙니다...")
|
|
converter.close()
|
|
except FileNotFoundError:
|
|
print(f"\n[에러] 파일을 찾을 수 없습니다: {html_path}")
|
|
except Exception as e:
|
|
print(f"\n[에러] {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()</code></pre>
|
|
</div>
|
|
|
|
<!-- 경로 수정 안내 -->
|
|
<div class="bg-white rounded-xl shadow-md p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-4">4. 경로 수정</h2>
|
|
<p class="text-gray-700 mb-4">스크립트 하단의 <code class="bg-gray-100 px-2 py-1 rounded">main()</code> 함수에서 경로를 수정하세요:</p>
|
|
<pre><code>html_path = r"C:\다운로드경로\report.html"
|
|
output_path = r"C:\저장경로\report.hwp"</code></pre>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
function copyScript() {
|
|
const code = document.getElementById('scriptCode').innerText;
|
|
navigator.clipboard.writeText(code).then(() => {
|
|
alert('스크립트가 클립보드에 복사되었습니다!');
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|