Skip to content

feat(markdown): Markdown → HWPX 쓰기(from_markdown)와 깨끗한 새 문서 모드 - #670

Merged
airmang merged 3 commits into
mainfrom
feat/from-markdown-669
Nov 7, 2026
Merged

airmang merged 3 commits into
mainfrom
feat/from-markdown-669

Conversation

@airmang

@airmang airmang commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Closes #669

무엇을 바꾸나

1. Markdown → HWPX

  • hwpx.tools.markdown_import.from_markdown(markdown, style=None) — 흔히 쓰는 Markdown 부분 집합으로 새 문서를 만든다.
  • doc.text.append_markdown(markdown, style=None) — 이미 있는 문서 끝에 덧붙인다.
  • 지원 범위
    • #~#### 제목: 굵게·크기, 개요 번호는 붙이지 않는다. #####·######은 ####처럼 그린다.
    • 문단: 이어진 줄은 공백 하나로 잇는다. 줄 끝 공백 둘이나 \는 줄 바꿈이다.
    • -/*/+ 글머리표와 1./1) 번호 목록: 들여쓰기로 수준을 나누고 doc.styles.apply_list_format으로 한/글 목록을 적용한다. 시작 번호를 지키고, 앞 문단 바로 뒤에 와도 목록으로 읽는다.
    • | 표: |---| 구분선 위 행은 굵게·음영 머리글로 그린다. 짧은 행은 빈 칸으로 채우고, 칸 안 <br>은 줄 바꿈이다.
    • 인라인: **굵게**·__굵게__, *기울임*, ***둘 다***. `코드`와 [글](주소)는 글자만 남기고, \ 이스케이프를 따른다.
    • 코드 블록은 줄을 지킨 문단 하나로 쓰고, 인용·HTML은 평문으로 쓰며, 수평선은 버린다. 모르는 Markdown으로 오류를 내지 않는다.
  • 모양은 장르 중립 MarkdownStyle(frozen dataclass)로 바꾼다. 기본값: # 16pt, ## 12pt, ### 11pt, #### 10pt(모두 굵게), 머리글 음영 #D9D9D9. 제목 위·아래 간격과 본문 크기, 글머리표 문자도 이 객체로 정한다.
  • 새 요소를 만들지 않는다. 기존 저작 primitive(add_paragraph·add_run·add_table·set_cell_shading·styles.ensure_run·apply_list_format·apply_paragraph_format)만 쓰며, 새 의존성도 없다.

2. 깨끗한 새 문서 모드

  • 원인: add_paragraph의 기본값은 마지막 문단의 paraPrIDRef/styleIDRef/charPrIDRef를 물려받는다. 그래서 add_heading이나 목록 바로 뒤에 붙인 문단이 개요·목록 번호를 함께 받았다.
  • HwpxDocument.new(inherit_style=False)로 만든 문서에서는 add_paragraph가 바탕글 모양에서 시작한다. 호출할 때 inherit_style=True를 주면 전처럼 물려받는다.
  • add_paragraph(inherit_style=None)의 None은 "문서 설정을 따름"이다. 기본 문서는 전처럼 물려받으므로 기존 동작은 바뀌지 않는다.

배치 결정

  • 장르 프리셋(official 공문, notice 가정통신문 등)은 core에 넣지 않았다. 계층 가드레일상 장르·기관 판단은 응용 계층(automation/skill)의 몫이다. core는 장르 중립 기본 모양과 덮어쓸 수 있는 MarkdownStyle만 제공하고, 프리셋은 automation이 이 스타일을 만들어 넘기면 된다.
  • 이슈는 HwpxDocument.from_markdown을 제안했다. 하지만 6.x 루트 표면이 예산 한도(35/35, 1-in-1-out)에 차 있어 생성자를 모듈 함수로 두었다. mail_merge·document_merge처럼 hwpx.tools 모듈 진입점이다. 루트 칸을 하나 비워 옮기는 것은 별도 결정으로 남긴다.

계약·문서

  • capabilities에 markdown-import 영역을 등재했다(namespace 없음, 모듈 진입점). support-matrix에는 Create(experimental) 행을 더하고 편집기 표면 인벤토리를 다시 생성했다.
  • 루트 표면 스냅숏에는 new·add_paragraph의 시그니처 변경만 반영된다(멤버 수는 그대로).
  • CHANGELOG [Unreleased] > 추가와 docs/usage.md 「Markdown으로 문서 만들기」(실행되는 standalone 예제)를 추가했다.

검증

  • tests/test_markdown_import.py 21건이 통과했다. 각 구성 요소(인라인 파싱, 제목·문단·목록 수준·번호 시작·표 머리글 음영과 굵게)를 확인한다. 개요·목록 번호 누수가 없는지, 저장·재열기 텍스트가 같은지, validate_editor_open_safety가 ok인지, 모르는 Markdown이 오류를 내지 않는지, 기존 문서에 덧붙이기와 상속 기본/깨끗한 모드도 확인한다.
  • 표면·계약 게이트(test_size_ratchet, test_namespace_hygiene, test_capabilities_surface, test_contract_docs_sync, test_document_facade_surface, test_engine_surface_6_0, test_stable_surface, test_golden_api, test_editor_surface_inventory, test_documentation_code_fences)가 모두 통과했다.
  • add_paragraph/inherit_style/스타일·목록·제목 관련 테스트 480건이 통과했다.
  • ruff check --select E9,F .과 scripts/check_public_hygiene.py가 통과했다.
  • 실한컴 렌더 판정은 아직 하지 않았다. 그래서 support-matrix 등급을 Create(experimental)로 두었다.
  • 참고: 목록을 쓴 문서는 validate()에서 head:bullets 스키마 순서 경고 1건이 나온다. 기존 apply_list_format에서도 똑같이 나는 경고라 이 PR과는 무관하다.

🤖 Generated with Claude Code

…-document mode

`hwpx.tools.markdown_import.from_markdown(markdown, style=...)` builds a new
document from the Markdown models write most reliably: # to #### headings,
paragraphs, -/*/1. lists nested by indentation (applied as Hancom lists),
pipe tables with a bold, shaded header row, and **bold**/*italic* inline.
Anything else is written as plain text; unknown Markdown never raises.
`doc.text.append_markdown(...)` adds the same to an existing document.
`MarkdownStyle` is a genre-neutral, overridable look; genre presets belong
to the application layer.

`HwpxDocument.new(inherit_style=False)` makes a clean document in which
`add_paragraph` starts from the base shapes instead of copying the last
paragraph, so a heading's outline or a list's numbering does not follow
into the next paragraph. `add_paragraph(inherit_style=None)` now follows
the document; the default document still inherits.

Closes #669

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt module

MarkdownStyle now raises HwpxValueError (text-markdown-style-invalid), keeping
the untyped-error census flat, and tools/markdown_import.py gets its explicit
ownership exception, so the boundary ratchet classifies it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@airmang airmang left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head adc60bf / base d1c7d0a1da5b083339616f19edd9c0dd4a805dfb를 검토했습니다. 새 파서·작성기와 new/add_paragraph의 상속 기본 호환성, namespace/문서 변경을 읽었고 tests/test_markdown_import.py 21개가 통과했습니다. 정확한 head CI 7,669 passed/18 skipped/1 xfailed 및 Ruff/mypy/Pyright 로그도 확인했습니다. 다만 공개 from_markdown 호출에서 코드 블록과 제목의 원문이 사라지는 두 P2를 재현해 병합을 보류합니다. 자기 PR이므로 COMMENT로 기록합니다.

body.append(lines[i])
i += 1
i += 1 # the closing fence (or the end)
yield _Paragraph("\n".join(body))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] fenced code의 본문은 인라인 Markdown을 다시 해석하지 않게 구분해 주세요. 여기서 일반 _Paragraph로 내보내면 _Writer.paragraph→write_runs→parse_inline을 타서 코드의 기호가 사라집니다. 공개 API from_markdown('text\n**literal** and [label](url)\n')의 문단 텍스트가 'literal and label'가 아니라 'literal and label'이고 literal에는 굵게까지 적용됩니다. 코드 예제의 실제 문자열/구문을 바꾸는 결과이므로 코드 블록 타입 또는 plain-text 작성 경로를 두고, 별표·링크·
를 포함한 코드의 저장/재열기 원문 보존 회귀를 추가해 주세요.

header: bool


_HEADING = re.compile(r"^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 공백 없이 제목에 붙은 #는 글자로 보존해 주세요. 끝부분의 \s*#*는 닫는 ATX 표지 앞 공백을 요구하지 않아 공개 from_markdown('# C#')가 제목 'C'를 만듭니다. '# topic#'도 마지막 글자가 사라집니다. 닫는 # 묶음은 앞에 공백이 있는 경우만 제거하고 '# C#'와 '# 제목 ###'를 함께 검증해 주세요.

Both sides added one entry to the same lists; keep both:
- errors.py / docs/error-codes.md: text-markdown-style-invalid next to
  text-outline-budget and text-block-unknown (count 200)
- module-ownership.json: tools/markdown_import.py and _document/text_blocks.py
- test_product_boundary: classifiedFiles 186 (+1 each)
- editor-surface-inventory: both rows; auto section regenerated (43 areas)
- CHANGELOG: both Unreleased entries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@airmang airmang left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최신 head 재검토: 이전 검토 SHA와 비교해 markdown_import.py 및 해당 테스트의 변경이 없음을 확인했습니다. 두 공개 API 재현에서 '# C#'→'C', fenced 'literal and label'→'literal and label'가 여전히 발생합니다. CI 13개 성공(7687 passed,18 skipped,1 xfailed 및 정적 검사 통과)은 확인했지만 기존 두 미해결 스레드의 원문 손실 결함 때문에 병합을 보류합니다. 중복 inline 지적은 추가하지 않습니다.

@airmang
airmang merged commit 168fe98 into main Nov 7, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

마크다운 → HWPX 코어 지원 from_markdown(preset) + 깨끗한 새 문서 모드

1 participant