← 메인으로

HWP 변환 가이드

⚠️ HWP 변환 요구사항

1. 필요 라이브러리 설치

pip install pyhwpx beautifulsoup4

2. 사용 방법

  1. 글벗 Light에서 HTML 파일을 다운로드합니다.
  2. 아래 Python 스크립트를 다운로드합니다.
  3. 스크립트 내 경로를 수정합니다.
  4. 스크립트를 실행합니다.

3. HWP 변환 스크립트

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

4. 경로 수정

스크립트 하단의 main() 함수에서 경로를 수정하세요:

html_path = r"C:\다운로드경로\report.html"
output_path = r"C:\저장경로\report.hwp"