Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

변환과 내보내기

convert_md — Markdown에서 문서 만들기

import hwpforge

source = """---
title: 보고서
---

# 개요

본문입니다.

| 항목 | 값 |
| -- | -- |
| 예산 | 1,000 |
"""
result = hwpforge.convert_md(source, preset="classic")
print(result.report["sections"], result.report["paragraphs"])
print(result.document.inspect()["metadata"]["title"])     # 보고서
result.document.save("report.hwpx")
  • Markdown은 GFM이고, 맨 앞의 YAML frontmatter가 문서 메타데이터와 스타일을 정합니다(title 등). 스타일 문법은 Markdown에서 HWPX 로를 보세요.
  • preset은 hwpforge.templates()가 나열하는 이름 중 하나입니다(default·modern·classic·latest). 없는 이름은 PRESET_NOT_FOUND.
  • 이미지 ![…](path)는 base_dir를 기준으로 읽습니다. base_dir를 주지 않으면 파일 이미지는 버려지고 보고서에 남습니다. base_dir 밖의 경로는 읽지 않습니다.
result = hwpforge.convert_md("# 그림\n\n![도표](chart.png)\n")     # base_dir 없음
print(result.report["assets"])
# [{'kind': 'dropped', 'occurrence': {'paragraph': 1, 'run': 0}, 'reason': 'no_base_dir'}]
print([w["code"] for w in result.report["warnings"]])              # ['IMAGE_EMBED_SKIPPED']

assets의 각 항목은 kind로 구분됩니다: embedded(key·format 포함), dropped(reason 포함), remote(URL은 내려받지 않습니다).

to_md — 문서를 Markdown으로

doc = hwpforge.Document.open("hancom.hwpx")
styled = doc.to_md()                       # mode="styled": 스타일 frontmatter 유지
lossy = doc.to_md(mode="lossy")            # Markdown 이 못 담는 것은 버리고 warnings 로 알림
print(lossy.text[:200])
print(lossy.report["mode"], list(lossy.report["images"].keys()))
  • mode="lossless"는 무언가를 잃어야 하면 ENCODE_FAILED로 거부합니다.
  • 보고서의 images는 문서가 참조한 이미지의 {키: 바이트}입니다. Markdown 텍스트가 그 키를 가리키므로, 파일로 저장할 때 같은 이름으로 옆에 써 두면 됩니다.

to_json · from_json — 전체 문서를 JSON으로

import json

exported = doc.to_json()                            # styles=True 가 기본
document = json.loads(exported.text)                # 키: document, styles
print(exported.report["document"] == document)      # 같은 내용을 dict 로도 줍니다

rebuilt = hwpforge.from_json(exported.text, base=doc)
print(rebuilt.report["paragraphs"], [w["code"] for w in rebuilt.report["warnings"]])
# 3 ['LAYOUT_CACHE_DROPPED']
  • to_json의 JSON은 문서 전체와 style store이며, 표 셀에 격자 주소가 붙어 있습니다. styles=False는 구조만 내보냅니다.
  • from_json은 그 JSON을 다시 인코드해 새 문서를 만듭니다. base를 주면 style store(스타일 없이 내보낸 JSON 일 때)와 이미지 바이너리를 그 문서에서 가져옵니다. base 없이 이미지가 있는 문서를 재구성하면 이미지가 빠집니다.
  • 생성은 fail-closed가 아닙니다: 의미가 손실돼도 거부하지 않고 warnings에 실어 돌려줍니다. 위의 LAYOUT_CACHE_DROPPED는 원본이 가진 줄 조판 캐시를 재인코드가 싣지 않는다는 뜻입니다(한컴에서 다시 저장하면 복원).
  • 한컴 저장 문서의 Preview/*·container.rdf 엔트리는 base를 줘도 승계되지 않습니다(경고 없음). 텍스트만 바꿀 거라면 export_section → patch가 문서를 온전히 보존합니다.
  • JSON이 문서가 아니면 JSON_PARSE_FAILED. 스키마는 hwpforge.schema(kind="exported-document").

전체 JSON(document/styles)과 구역 JSON(section_index/section/styles/preservation)은 서로 다른 스키마입니다 — 전자는 from_json, 후자는 patch가 읽습니다.

convert_hwp5 — 옛 .hwp를 HWPX로

with open("old.hwp", "rb") as handle:
    converted = hwpforge.convert_hwp5(handle.read())
print(len(converted.document), [w["code"] for w in converted.report["warnings"]])
converted.document.save("old.hwpx")
  • 인자는 파일 경로가 아니라 바이트입니다(Document.open은 HWPX 전용입니다). 읽을 수 없는 바이트는 HWP5_DECODE_FAILED.
  • 보고서의 warnings에 HWP5가 가졌지만 HWPX로 옮기지 못한 것이 전부 나옵니다.
  • carry_layout_cache=True는 한컴이 계산해 둔 줄 조판 캐시를 함께 옮깁니다. PDF 렌더가 한컴의 쪽 나눔을 그대로 재현하려면 이 캐시가 필요합니다(아래). 캐시를 옮긴 HWPX는 PDF 재생·비교용이며, 한컴에서 다시 열어 편집할 문서로 취급하지 마세요.

to_pdf — PDF 렌더

렌더는 문서에 저장된 조판을 재생합니다(다시 계산하지 않습니다). 그래서 조판 캐시가 있는 문서만 렌더 대상입니다: 한컴이 저장한 HWPX, 그리고 convert_hwp5(..., carry_layout_cache=True)로 캐시를 옮긴 HWPX. Markdown·JSON에서 생성한 문서는 캐시가 없어 PDF_RENDER_FAILED입니다.

캐시가 있어도 아직 재생하지 못하는 내용이 있습니다. 한컴이 저장한 문서라도 아래 경우는 실패하고, 한컴에서 다시 저장해도 풀리지 않습니다.

cause["code"]언제예
INVALID_CACHE문단 안에 글자가 아닌 요소(누름틀·메모·각주·수식·하이퍼링크·상호참조·차트·도형 등)가 있음누름틀이 든 서식 (clickhere_filled.hwpx)
UNSUPPORTED_CONTENT아직 지원하지 않는 배치 — cause["kind"]가 무엇인지 말함kind == "non-default table position" (table_20_real_world_ministry_stress.hwpx)

누름틀 서식처럼 이 경우에 걸리는 문서는 한컴에서 PDF로 저장하세요.

with open("old.hwp", "rb") as handle:
    carried = hwpforge.convert_hwp5(handle.read(), carry_layout_cache=True).document

try:
    pdf = carried.to_pdf(font_dirs=["/Library/Fonts/Hancom"])
    print(pdf.report["pages"])
    with open("old.pdf", "wb") as out:
        out.write(pdf.data)
except hwpforge.HwpForgeError as exc:
    print(exc.code, exc.cause)
    # PDF_RENDER_FAILED {'stage': 'render', 'code': 'FONT_UNRESOLVED'}      ← 글꼴을 못 찾음
    # PDF_RENDER_FAILED {'stage': 'render', 'code': 'NO_RENDERABLE_CACHE', 'location': 's0'}  ← 캐시 없음
    # PDF_RENDER_FAILED {'stage': 'render', 'code': 'INVALID_CACHE'}        ← 재생할 수 없는 요소가 든 문단
  • 글꼴은 기본 fail-closed입니다: 문서가 이름 붙인 글꼴 face를 font_dirs 안에서 찾지 못하면 추측하지 않고 실패합니다(cause["code"] == "FONT_UNRESOLVED"). degraded=True는 대체 글꼴로 렌더하며, 결과의 모양이 달라집니다.
  • discovery는 font_dirs 밖을 더 볼지의 선택입니다: "explicit"(기본, 결정적) · "hancom"(한컴 설치 글꼴 위치) · "platform"(OS 글꼴).
  • font_dirs는 시퀀스여야 합니다. 문자열 하나를 주면 한 글자짜리 디렉터리들로 읽히는 대신 TypeError로 거부됩니다.
  • partial_cache_reject=True는 캐시가 일부만 있는 문서를 다시 배치하지 않고 거부합니다.
  • 실패의 두 번째 분류는 exc.cause에 옵니다(stage·code·kind·location). exc.hint는 원인과 상관없이 같은 문장이므로, 무엇을 할지는 cause["code"]로 가르세요: FONT_UNRESOLVED는 font_dirs·discovery, NO_RENDERABLE_CACHE·MISSING_LAYOUT_CACHE는 한컴에서 다시 저장, INVALID_CACHE·UNSUPPORTED_CONTENT는 위 표.

templates · schema

for preset in hwpforge.templates()["presets"]:
    print(preset["name"], preset["description"], preset["font"], preset["page_size"])

schema = hwpforge.schema(kind="exported-section")   # "document" | "exported-document" | "exported-section"
print(schema["title"], list(schema["properties"])[:4])

두 함수의 결과에는 warnings가 없습니다. schema는 JSON Schema dict이며 키는 스키마 자신의 것입니다.

by Ai-Scream