hwp2markdown 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hwp2markdown-0.1.0/.gitignore +58 -0
- hwp2markdown-0.1.0/LICENSE +21 -0
- hwp2markdown-0.1.0/NOTICE +64 -0
- hwp2markdown-0.1.0/PKG-INFO +337 -0
- hwp2markdown-0.1.0/README.md +322 -0
- hwp2markdown-0.1.0/pyproject.toml +66 -0
- hwp2markdown-0.1.0/src/hwp2md/__init__.py +44 -0
- hwp2markdown-0.1.0/src/hwp2md/blocks.py +160 -0
- hwp2markdown-0.1.0/src/hwp2md/cli.py +94 -0
- hwp2markdown-0.1.0/src/hwp2md/convert.py +104 -0
- hwp2markdown-0.1.0/src/hwp2md/detect.py +68 -0
- hwp2markdown-0.1.0/src/hwp2md/equation/__init__.py +22 -0
- hwp2markdown-0.1.0/src/hwp2md/equation/eqedit.py +380 -0
- hwp2markdown-0.1.0/src/hwp2md/equation/tokens.py +292 -0
- hwp2markdown-0.1.0/src/hwp2md/errors.py +29 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/__init__.py +1 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/aes.py +167 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/body.py +714 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/distdoc.py +55 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/docinfo.py +227 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/reader.py +264 -0
- hwp2markdown-0.1.0/src/hwp2md/hwp5/records.py +165 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpml/__init__.py +1 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpml/body.py +523 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpml/reader.py +350 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpx/__init__.py +1 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpx/bindata.py +89 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpx/body.py +554 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpx/header.py +161 -0
- hwp2markdown-0.1.0/src/hwp2md/hwpx/reader.py +163 -0
- hwp2markdown-0.1.0/src/hwp2md/images.py +45 -0
- hwp2markdown-0.1.0/src/hwp2md/memory.py +185 -0
- hwp2markdown-0.1.0/src/hwp2md/numbering.py +138 -0
- hwp2markdown-0.1.0/src/hwp2md/ole.py +184 -0
- hwp2markdown-0.1.0/src/hwp2md/py.typed +0 -0
- hwp2markdown-0.1.0/src/hwp2md/render.py +188 -0
- hwp2markdown-0.1.0/src/hwp2md/skips.py +45 -0
- hwp2markdown-0.1.0/src/hwp2md/source.py +25 -0
- hwp2markdown-0.1.0/src/hwp2md/web/__init__.py +1 -0
- hwp2markdown-0.1.0/src/hwp2md/web/app.py +134 -0
- hwp2markdown-0.1.0/src/hwp2md/web/server.py +48 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/about.html +483 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/css/app.css +252 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/css/base.css +277 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/css/landing.css +415 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/index.html +156 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/js/app.js +432 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/js/effects.js +130 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/js/landing.js +515 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/js/theme.js +55 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/pyodide-runner.js +113 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/README.md +23 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/JetBrainsMono-Medium.woff2 +0 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/JetBrainsMono-Regular.woff2 +0 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/JetBrainsMono-SemiBold.woff2 +0 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/OFL-JetBrainsMono.txt +93 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/OFL-Pretendard.txt +94 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/PretendardVariable.woff2 +0 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/fonts/fonts.css +30 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/gsap/LICENSE.md +15 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/gsap/ScrambleTextPlugin.min.js +11 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/gsap/ScrollTrigger.min.js +11 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/gsap/SplitText.min.js +11 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/gsap/gsap.min.js +11 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/marked.LICENSE.md +44 -0
- hwp2markdown-0.1.0/src/hwp2md/web/static/vendor/marked.min.js +6 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# Env / secrets
|
|
6
|
+
.env
|
|
7
|
+
.env.*
|
|
8
|
+
!.env.example
|
|
9
|
+
|
|
10
|
+
# Python
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
|
|
22
|
+
# Node
|
|
23
|
+
node_modules/
|
|
24
|
+
npm-debug.log*
|
|
25
|
+
yarn-error.log*
|
|
26
|
+
.next/
|
|
27
|
+
.turbo/
|
|
28
|
+
coverage/
|
|
29
|
+
|
|
30
|
+
# Editors
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
|
|
34
|
+
# Logs / data
|
|
35
|
+
*.log
|
|
36
|
+
data/raw/
|
|
37
|
+
.ai/tasks/*/logs/
|
|
38
|
+
.ai/tasks/*/screenshots/*.png
|
|
39
|
+
.ai/tasks/*/.toolcount
|
|
40
|
+
.ai/tasks/*/.*.lock
|
|
41
|
+
.ai/tasks/*/.dev.pid
|
|
42
|
+
.ai/tasks/*/.base-*
|
|
43
|
+
.ai/headless-*.log
|
|
44
|
+
.ai/.headless-session
|
|
45
|
+
.claude/settings.local.json
|
|
46
|
+
out/
|
|
47
|
+
test-results/
|
|
48
|
+
playwright-report/
|
|
49
|
+
*.pyc
|
|
50
|
+
|
|
51
|
+
# real HWP samples (license unverified) and E2E evidence
|
|
52
|
+
tests/fixtures/**
|
|
53
|
+
!tests/fixtures/.gitkeep
|
|
54
|
+
!tests/fixtures/README.md
|
|
55
|
+
tests/e2e/screenshots/
|
|
56
|
+
|
|
57
|
+
# ์ค์ ์ํ ๋ณํ ๊ฒฐ๊ณผยท์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ๋ ์ฌ๋ฆฌ์ง ์๋๋ค (๋ฌธ์ ๋ด์ฉ ํฌํจ)
|
|
58
|
+
.ai/tasks/*/screenshots/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hwp2md contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
hwp2md
|
|
2
|
+
Third-party notices
|
|
3
|
+
|
|
4
|
+
hwp2md is licensed under the MIT License (see LICENSE). The vendored files
|
|
5
|
+
listed below remain under their original licenses.
|
|
6
|
+
|
|
7
|
+
------------------------------------------------------------------------
|
|
8
|
+
HWP document format public specification
|
|
9
|
+
|
|
10
|
+
๋ณธ ์ ํ์ ํ๊ธ๊ณผ์ปดํจํฐ์ ํ/๊ธ ๋ฌธ์ ํ์ผ(.hwp) ๊ณต๊ฐ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ๊ฐ๋ฐํ์์ต๋๋ค.
|
|
11
|
+
|
|
12
|
+
------------------------------------------------------------------------
|
|
13
|
+
python-hwpx โ equation converter (vendored)
|
|
14
|
+
|
|
15
|
+
Source repository: https://github.com/airmang/python-hwpx
|
|
16
|
+
Commit: 7ca45822076b9feb7cf3a7cfb2c924d121162b4b
|
|
17
|
+
License: Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
18
|
+
Copyright 2025-2026 airmang
|
|
19
|
+
|
|
20
|
+
Vendored files:
|
|
21
|
+
src/hwpx/equation/eqedit.py -> src/hwp2md/equation/eqedit.py
|
|
22
|
+
src/hwpx/equation/tokens.py -> src/hwp2md/equation/tokens.py
|
|
23
|
+
|
|
24
|
+
Changes made:
|
|
25
|
+
- Moved into the package hwp2md.equation (relative imports unchanged).
|
|
26
|
+
- eqedit.py: added a "# ruff: noqa: E501" line after the SPDX header so the
|
|
27
|
+
project linter accepts the original long comment lines. No logic changes.
|
|
28
|
+
- Added src/hwp2md/equation/__init__.py (new, not from python-hwpx) with a
|
|
29
|
+
to_latex() wrapper that returns None on empty input or conversion failure.
|
|
30
|
+
|
|
31
|
+
Upstream NOTICE excerpt (python-hwpx):
|
|
32
|
+
This product includes software developed by airmang.
|
|
33
|
+
Licensed under the Apache License, Version 2.0.
|
|
34
|
+
- OpenBapul/hml-equation-parser (Apache-2.0): HULK-style EqEdit <-> LaTeX
|
|
35
|
+
token mapping, referenced for reader-direction <hp:equation> script
|
|
36
|
+
rendering. The token maps are a clean-room re-derivation from the public
|
|
37
|
+
EqEdit grammar; no source code was copied, translated, or vendored.
|
|
38
|
+
|
|
39
|
+
------------------------------------------------------------------------
|
|
40
|
+
Web UI assets (vendored, unmodified) โ src/hwp2md/web/static/vendor/
|
|
41
|
+
Versions, source URLs and SHA-256: src/hwp2md/web/static/vendor/README.md
|
|
42
|
+
|
|
43
|
+
marked 12.0.2
|
|
44
|
+
Files: vendor/marked.min.js
|
|
45
|
+
License: MIT (vendor/marked.LICENSE.md)
|
|
46
|
+
Source: https://github.com/markedjs/marked
|
|
47
|
+
|
|
48
|
+
GSAP 3.13.0 (gsap, ScrollTrigger, SplitText, ScrambleTextPlugin)
|
|
49
|
+
Files: vendor/gsap/gsap.min.js, ScrollTrigger.min.js, SplitText.min.js,
|
|
50
|
+
ScrambleTextPlugin.min.js
|
|
51
|
+
License: GSAP Standard "no charge" license (vendor/gsap/LICENSE.md)
|
|
52
|
+
License URL: https://gsap.com/standard-license
|
|
53
|
+
Source: https://www.npmjs.com/package/gsap
|
|
54
|
+
|
|
55
|
+
Pretendard 1.3.9 (Pretendard Variable)
|
|
56
|
+
Files: vendor/fonts/PretendardVariable.woff2
|
|
57
|
+
License: SIL Open Font License 1.1 (vendor/fonts/OFL-Pretendard.txt)
|
|
58
|
+
Source: https://github.com/orioncactus/pretendard
|
|
59
|
+
|
|
60
|
+
JetBrains Mono 2.304 (Regular, Medium, SemiBold)
|
|
61
|
+
Files: vendor/fonts/JetBrainsMono-Regular.woff2, JetBrainsMono-Medium.woff2,
|
|
62
|
+
JetBrainsMono-SemiBold.woff2
|
|
63
|
+
License: SIL Open Font License 1.1 (vendor/fonts/OFL-JetBrainsMono.txt)
|
|
64
|
+
Source: https://github.com/JetBrains/JetBrainsMono
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: hwp2markdown
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert HWP 5.0 documents to Markdown without Hancom Office
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: NOTICE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: lxml>=5
|
|
10
|
+
Requires-Dist: olefile>=0.46
|
|
11
|
+
Provides-Extra: web
|
|
12
|
+
Requires-Dist: starlette>=0.37; extra == 'web'
|
|
13
|
+
Requires-Dist: uvicorn>=0.30; extra == 'web'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# hwp2md
|
|
17
|
+
|
|
18
|
+
ํ์ปด์คํผ์ค ์์ด HWPยทHWPXยทHWPML ๋ฌธ์๋ฅผ ์ ๋ชฉ ๊ตฌ์กฐยทํยท์ด๋ฏธ์งยท๊ฐ์ฃผยท์์์ด ์ด์ ์๋ Markdown ์ผ๋ก ๋ฐ๊พธ๋ ์์ ํ์ด์ฌ CLIยท๋ผ์ด๋ธ๋ฌ๋ฆฌยท๋ก์ปฌ ์น ๋ฏธ๋ฆฌ๋ณด๊ธฐ.
|
|
19
|
+
|
|
20
|
+
๋ณธ ์ ํ์ ํ๊ธ๊ณผ์ปดํจํฐ์ ํ/๊ธ ๋ฌธ์ ํ์ผ(.hwp) ๊ณต๊ฐ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ๊ฐ๋ฐํ์์ต๋๋ค.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
> ํ๋ฉด์ ๋ฌธ์๋ ์์ฐ์ฉ์ผ๋ก ์ฝ๋์์ ๋ง๋ ๊ฐ์์ ๊ณต๊ณ ๋ฌธ์ด๋ค. ์ค์ ๊ธฐ๊ดยท๋ฌธ์์ ๊ด๊ณ์๋ค.
|
|
25
|
+
|
|
26
|
+
## ์ ํ์ํ๊ฐ
|
|
27
|
+
|
|
28
|
+
ํ๊ตญ์ ๊ณต๊ณตยท๊ธฐ์
๋ฌธ์๋ HWP ์์ ๊ฐํ ์๋ค. LLM ์๋์ ๊ฐ์ฅ ๋จผ์ ํ์ํ ๊ฑด ๊ทธ ๋ฌธ์ ์ฌ๋ ์ด์ ๋ค.
|
|
29
|
+
|
|
30
|
+
- HWP/HWPX ๋ RAG ํ์ดํ๋ผ์ธ, LLM ์
๋ ฅ, ๋ฒ์ ๊ด๋ฆฌ ์ด๋์๋ ๋ฐ๋ก ์ธ ์ ์๋ค.
|
|
31
|
+
- ๊ธฐ์กด ๋ณํ ๋๊ตฌ๋ ํยท์ด๋ฏธ์ง ์์น๋ฅผ ์๊ฑฐ๋, ํ์ปด ์ค์น๋ฅผ ์๊ตฌํ๋ค.
|
|
32
|
+
- ๊ฐ๋ฐ์๋ค์ด ๋งค๋ฒ ๊ฐ์ ํ์ฑ ์ฝ๋๋ฅผ ๋ค์ ์ง ๋ค.
|
|
33
|
+
|
|
34
|
+
hwp2md ๋ ์ด ๋ฌธ์ ๋ฅผ ์ด๋ ๊ฒ ํผ๋ค.
|
|
35
|
+
|
|
36
|
+
- ํ์ปด ์์ด ๋์ํ๋ ์์ ํ์ด์ฌ (์์กด์ฑ `olefile`, `lxml` ๋ ๊ฐ).
|
|
37
|
+
- ํ๋ Markdown ํ๋ก, ์ด๋ฏธ์ง๋ ํ์ผ๋ก ์ถ์ถํด ์๋ ๊ฒฝ๋ก๋ก ๋งํฌํ๋ค.
|
|
38
|
+
- ๋ฌธ์์ ์ ๋ชฉ ๊ณ์ธต(๊ฐ์ ์์ค)์ `#` ์ ๋ชฉ์ผ๋ก ๋ณด์กดํด ์ฒญํน์ ์ ๋ฆฌํ๊ฒ ๋ง๋ ๋ค.
|
|
39
|
+
- ๊ฐ์ ์์ง์ CLIยท๋ผ์ด๋ธ๋ฌ๋ฆฌยท์น ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ํจ๊ป ์ด๋ค. ์ธ ๊ณณ์ ์ถ๋ ฅ์ ๋ฐ์ดํธ ๋จ์๋ก ๊ฐ๋ค.
|
|
40
|
+
|
|
41
|
+
## ๋น ๋ฅธ ์์
|
|
42
|
+
|
|
43
|
+
### ์ค์น
|
|
44
|
+
|
|
45
|
+
PyPI ๋ฐฐํฌ ์ด๋ฆ์ `hwp2markdown` ์ด๋ค. ์ค์น ํ ๋ช
๋ น๊ณผ import ์ด๋ฆ์ `hwp2md` ๋ค. Python 3.11 ์ด์์ด ํ์ํ๋ค.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install 'hwp2markdown[web]' # ๋๋: uv tool install 'hwp2markdown[web]'
|
|
49
|
+
hwp2md --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
PyPI ์ `hwp2md` ๋ ์ด ์ ์ฅ์์ ๋ฌด๊ดํ ๋ค๋ฅธ ํ๋ก์ ํธ๋ค. ์ค์นํ ๋๋ `hwp2markdown` ์ ์ด๋ค.
|
|
53
|
+
|
|
54
|
+
์น ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์์กด์ฑ(Starletteยทuvicorn)์ extra `web` ์๋ง ์๋ค. ๊ธฐ๋ณธ ์ค์น(`pip install hwp2markdown`, CLIยท๋ผ์ด๋ธ๋ฌ๋ฆฌ)๋ `olefile`ยท`lxml` ๋ง ์ด๋ค. extra ์์ด `hwp2md serve` ๋ฅผ ์คํํ๋ฉด `pip install 'hwp2markdown[web]'` ์๋ด์ ํจ๊ป ์ข
๋ฃ ์ฝ๋ 1๋ก ๋๋๋ค.
|
|
55
|
+
|
|
56
|
+
์์ค์์ ์ค์น(๊ฐ๋ฐ์ฉ):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone <์ด ์ ์ฅ์> hwp2md && cd hwp2md
|
|
60
|
+
|
|
61
|
+
# ๋ฐฉ๋ฒ 1: uv (์น ๋ฏธ๋ฆฌ๋ณด๊ธฐยทํ
์คํธ ๋๊ตฌ๊น์ง ํจ๊ป ์ค์น)
|
|
62
|
+
uv sync
|
|
63
|
+
uv run hwp2md --version
|
|
64
|
+
|
|
65
|
+
# ๋ฐฉ๋ฒ 2: pip (์น ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ํ์ ์์ผ๋ฉด '[web]' ์ ๋นผ๋ ๋๋ค)
|
|
66
|
+
pip install '.[web]'
|
|
67
|
+
hwp2md --version
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
์๋ ์์๋ `uv run` ์ ์๋ตํ๋ค. ์์ค์์ uv ๋ก ์ค์นํ๋ค๋ฉด ์์ `uv run` ์ ๋ถ์ธ๋ค.
|
|
71
|
+
|
|
72
|
+
### โ CLI
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
usage: hwp2md [-h] [-o OUT_DIR] [--version] INPUT
|
|
76
|
+
usage: hwp2md serve [-h] [--host HOST] [--port PORT] [--no-browser]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
์์ฐ์ฉ ํฉ์ฑ ๋ฌธ์(์ ๋ชฉ 5๊ฐยท๋ณธ๋ฌธ 5๋จ๋ฝยทํ 1๊ฐยท๊ทธ๋ฆผ 1๊ฐ)๋ฅผ ๋ณํํ ์ค์ ์ถ๋ ฅ์ด๋ค.
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
$ hwp2md demo.hwpx -o out/
|
|
83
|
+
โ demo.hwpx โ out/demo.md
|
|
84
|
+
๋จ๋ฝ 5๊ฐ ยท ํ 1๊ฐ ยท ์ ๋ชฉ 5๊ฐ ยท ์ด๋ฏธ์ง 1๊ฐ
|
|
85
|
+
์ด๋ฏธ์ง: out/demo_images/ (1 files)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
out/
|
|
90
|
+
โโโ demo.md
|
|
91
|
+
โโโ demo_images/
|
|
92
|
+
โโโ img_001.png
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`out/demo.md`:
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
# 2026๋
๋ ๊ฐ์ ๋ฌธ์ ๋์งํธ ์ ํ ์ง์์ฌ์
๊ณต๊ณ
|
|
99
|
+
|
|
100
|
+
์ด ๋ฌธ์๋ hwp2md ์์ฐ์ ์ํด ๋ง๋ ๊ฐ์์ ๊ณต๊ณ ๋ฌธ์
๋๋ค. ์ค์ ๊ธฐ๊ดยท์ฌ์
๊ณผ ๊ด๊ณ์์ต๋๋ค.
|
|
101
|
+
|
|
102
|
+
## 1. ์ฌ์
๊ฐ์
|
|
103
|
+
|
|
104
|
+
์ข
์ด์ ํ๊ธ ๋ฌธ์๋ก ๊ด๋ฆฌํ๋ ์
๋ฌด ์๋ฃ๋ฅผ ๊ฒ์ ๊ฐ๋ฅํ ํํ๋ก ๋ฐ๊พธ๋ ๊ธฐ๊ด์ ์ง์ํฉ๋๋ค.
|
|
105
|
+
|
|
106
|
+
| ๊ตฌ๋ถ | ๋ด์ฉ |
|
|
107
|
+
|---|---|
|
|
108
|
+
| ์ง์ ๊ท๋ชจ | ์ด 10์ต ์ (๊ธฐ๊ด๋น ์ต๋ 5์ฒ๋ง ์) |
|
|
109
|
+
| ์ง์ ๊ธฐ๊ฐ | 2026. 3. ~ 2026. 12. |
|
|
110
|
+
| ์ ์ฒญ ๋ฐฉ๋ฒ | ์จ๋ผ์ธ ์ ์ (๋ถ์ ์์ ์์ฑ ํ ์ ์ถ) |
|
|
111
|
+
|
|
112
|
+
## 2. ์ถ์ง ์ ์ฐจ
|
|
113
|
+
|
|
114
|
+
์ ์ฒญ์ ์ ์ ํ ์๋ฉด ์ฌ์ฌ๋ฅผ ๊ฑฐ์ณ ์ง์ ๋์์ ์ ์ ํฉ๋๋ค.
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
### 2.1 ์ฌ์ฌ ๊ธฐ์ค
|
|
119
|
+
|
|
120
|
+
์ฌ์
ํ์์ฑ, ์ถ์ง ๊ณํ์ ๊ตฌ์ฒด์ฑ, ์ฑ๊ณผ ํ์ฉ ๋ฐฉ์์ ํ๊ฐํฉ๋๋ค.
|
|
121
|
+
|
|
122
|
+
## 3. ๋ฌธ์
|
|
123
|
+
|
|
124
|
+
๋ฌธ์๋ ๊ฐ์ ์ฌ์
์ด์ ์ฌ๋ฌด๊ตญ์ผ๋ก ํด ์ฃผ์ญ์์ค.
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `-o` ๋ฅผ ์๋ตํ๋ฉด ํ์ฌ ํด๋์ ์ด๋ค. ์ถ๋ ฅ ํด๋๊ฐ ์์ผ๋ฉด ๋ง๋ ๋ค.
|
|
128
|
+
- HWP 5.0ยทHWPXยทHWPML ์ ๊ฐ์ ๋ช
๋ น์ด๋ค. ํฌ๋งท์ ํ์ฅ์๊ฐ ์๋๋ผ ํ์ผ ๋ด์ฉ์ผ๋ก ํ๋ณํ๋ค.
|
|
129
|
+
- ์ด๋ฏธ์ง๊ฐ ์๋ ๋ฌธ์๋ `_images` ํด๋๋ฅผ ๋ง๋ค์ง ์๊ณ , ์์ฝ ์ค์์ 0๊ฐ์ธ ์ ๋ชฉยท์ด๋ฏธ์ง ํญ๋ชฉ์ ์๋ตํ๋ค.
|
|
130
|
+
- ์ข
๋ฃ ์ฝ๋: 0 ์ฑ๊ณต ยท 1 ๋ณํ ์ค๋ฅ ยท 2 ํ์ผ ์์ ยท 3 HWP ๊ฐ ์๋๊ฑฐ๋ ์ง์ํ์ง ์๋ ๋ฌธ์(DRMยท๋น๋ฐ๋ฒํธ ๋ฌธ์ ๋ฑ). ์ค๋ฅ ์ค์ `unsupported HWP file (DRM protected): โฆ`, `unsupported HWP file (password protected): โฆ` ์ฒ๋ผ ๊ฑฐ๋ถ ์ด์ ๋ฅผ ๊ตฌ๋ถํ๋ค.
|
|
131
|
+
- ๋ณํํ์ง ๋ชปํ ์์๊ฐ ์์ผ๋ฉด ๋ง์ง๋ง์ ` ๊ฑด๋๋: ๋จธ๋ฆฌ๋งยท๊ผฌ๋ฆฌ๋ง 2๊ฐ ยท ๋ฉ๋ชจ 1๊ฐ` ๊ฐ์ ์ค์ด ๋ถ๋๋ค(์๋ "๊ฑด๋๋ ๋ณด๊ณ ").
|
|
132
|
+
- ๋ณํํ ํ์ผ ์ด๋ฆ์ด `serve` ์ด๋ฉด `hwp2md ./serve` ์ฒ๋ผ ๊ฒฝ๋ก๋ก ์ ๋๋ค.
|
|
133
|
+
|
|
134
|
+
### โก ์น ๋ฏธ๋ฆฌ๋ณด๊ธฐ
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
hwp2md serve # http://localhost:8770 , ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ์ด๋ฆฐ๋ค
|
|
138
|
+
hwp2md serve --port 8771 # 8770 ์ด ์ด๋ฏธ ์ฐ์ด๊ณ ์์ ๋
|
|
139
|
+
hwp2md serve --no-browser # ๋ธ๋ผ์ฐ์ ์๋ ์ด๊ธฐ ๋
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`/` ์ ๋ณํ ํ๋ฉด, `/about` ์ ์๋น์ค ์๊ฐ(๊ธฐ๋ฅ, ์ฌ์ฉ ์ฅ๋ฉด, ์ถ์ถ ํ๊ณ `/about#limits`)๋ค. ๋ ํ๋ฉด์ ์ฐ๋ ๊ธ๊ผดยทGSAPยทmarked ๋ ํจํค์ง ์์ ๋๋ด๋ผ ์์ด ์๋ฒ ๋ชจ๋๋ ์ธํฐ๋ท ์์ด ๋์ํ๋ค.
|
|
143
|
+
|
|
144
|
+
1. HWPยทHWPX ํ์ผ์ ๋์ด๋ค ๋๊ฑฐ๋ ํด๋ฆญํด์ ๊ณ ๋ฅธ๋ค (์ต๋ 50MB).
|
|
145
|
+
2. ์ผ์ชฝ์ ๋ ๋๋ง๋ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ์ค๋ฅธ์ชฝ์ Markdown ์๋ฌธ์ด ๋์จ๋ค.
|
|
146
|
+
3. "zip ๋ค์ด๋ก๋" ๋ก `.md` ์ ์ด๋ฏธ์ง ํด๋๋ฅผ ํ ๋ฒ์ ๋ฐ๋๋ค.
|
|
147
|
+
|
|
148
|
+
์ค๋ฅธ์ชฝ ์ "๋ธ๋ผ์ฐ์ ์์์๋ง ์ฒ๋ฆฌ" ์ค์์น๋ก ๋ณํ ์์น๋ฅผ ๊ณ ๋ฅธ๋ค.
|
|
149
|
+
|
|
150
|
+
| | ์๋ฒ ๋ชจ๋ (๊ธฐ๋ณธ) | ๋ธ๋ผ์ฐ์ ์์์๋ง ์ฒ๋ฆฌ |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| ๋ณํ ์์น | ๋ก์ปฌ `hwp2md serve` ํ๋ก์ธ์ค (๋ฉ๋ชจ๋ฆฌ์์ ๋ณํ) | ๋ธ๋ผ์ฐ์ ์์ Pyodide (WebAssembly ํ์ด์ฌ) |
|
|
153
|
+
| ํ์ผ ๋ฐ์ดํธ ์ ์ก | ๋ก์ปฌ ์๋ฒ๋ก `POST /api/convert` | ๋ณด๋ด์ง ์์ |
|
|
154
|
+
| ์ฒซ ๋ณํ๊น์ง | ์ฆ์ | ์ฒซ ๋ก๋ฉ ์ฝ 1๋ถ (Pyodideยทlxml ๋ค์ด๋ก๋, ์ธก์ ์ฝ 65์ด) |
|
|
155
|
+
| ์ธํฐ๋ท | ํ์ ์์ (ํ๋ฉด ์์ ๋๋ด) | ํ์ (Pyodide ๋ฅผ jsDelivr CDN ์์ ๋ฐ์) |
|
|
156
|
+
| ๊ฒฐ๊ณผ | ๊ฐ์ Markdown | ๊ฐ์ Markdown (E2E ๋ก ๋ฐ์ดํธ ๋น๊ต) |
|
|
157
|
+
|
|
158
|
+

|
|
159
|
+
|
|
160
|
+

|
|
161
|
+
|
|
162
|
+
### โข ๋ผ์ด๋ธ๋ฌ๋ฆฌ
|
|
163
|
+
|
|
164
|
+
ํ์ผ๋ก ์ฐ๊ธฐ (`convert`):
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from hwp2md import convert
|
|
168
|
+
|
|
169
|
+
result = convert("input.hwp", "out/") # out/input.md (+ out/input_images/)
|
|
170
|
+
print(result.output) # Path("out/input.md")
|
|
171
|
+
print(result.heading_count, result.table_count, result.image_count)
|
|
172
|
+
print(result.markdown) # ํ์ผ์ ์ด ๊ฒ๊ณผ ๊ฐ์ ๋ด์ฉ
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
๋ฉ๋ชจ๋ฆฌ์์๋ง ๋ณํํ๊ธฐ (`convert_bytes`, ๋์คํฌ์ ์๋ฌด๊ฒ๋ ์ฐ์ง ์๋๋ค):
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from pathlib import Path
|
|
179
|
+
from hwp2md import convert_bytes
|
|
180
|
+
|
|
181
|
+
data = Path("input.hwp").read_bytes()
|
|
182
|
+
result = convert_bytes(data, filename="input.hwp") # filename ์ ์ด๋ฏธ์ง ํด๋ ์ด๋ฆ์๋ง ์ฐ์ธ๋ค
|
|
183
|
+
print(result.markdown) # convert() ๊ฐ ์ฐ๋ .md ์ ๋ฐ์ดํธ ๋์ผ
|
|
184
|
+
for img in result.images: # ์ถ์ถ๋ ์ด๋ฏธ์ง (๊ณ ์ ํ์ผ๋ง)
|
|
185
|
+
print(img.file_name, img.mime, len(img.data))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- ์๊ทธ๋์ฒ: `convert(path: str | Path, out_dir: str | Path = ".") -> ConvertResult`, `convert_bytes(data: bytes, filename: str = "document.hwp") -> MemoryResult`.
|
|
189
|
+
- ์์ธ: `NotHwpFileError`(HWP ์๋), `UnsupportedHwpError`(์ง์ํ์ง ์๋ ๋ฌธ์)์ ๊ทธ ํ์ `DrmProtectedError`(DRM ์ ์ฉ ๋ฌธ์)ยท`EncryptedDocumentError`(๋น๋ฐ๋ฒํธ ๋ฌธ์), ๋ชจ๋ `Hwp2mdError` ํ์.
|
|
190
|
+
- ๊ฑด๋๋ด ์์: `result.skipped` ๋ `(("header_footer", 2), โฆ)` ์ฒ๋ผ ์ข
๋ฅ์ ๊ฐ์์ ํํ์ด๋ค(0๊ฐ๋ ๋น ์ง๋ค). `convert`ยท`convert_bytes` ๊ฒฐ๊ณผ ๋ชจ๋์ ์๋ค. `convert` ๋ ํ์ผ์ด ์์ผ๋ฉด `FileNotFoundError`, `convert_bytes` ๋ ๋น ์
๋ ฅยท50MB ์ด๊ณผ๋ `Hwp2mdError` ํ์ ์์ธ๋ก ๊ฑฐ๋ถํ๋ค.
|
|
191
|
+
|
|
192
|
+
## ์ง์ ๋ฒ์
|
|
193
|
+
|
|
194
|
+
| ๊ตฌ๋ถ | ํญ๋ชฉ | ์ค๋ช
|
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| ์ง์ | ํฌ๋งท | HWP 5.0 ๋ฐ์ด๋๋ฆฌ(OLE), HWPX(zip/XML), HWPML 2.x(๋จ์ผ XML). ํ์ฅ์๊ฐ ์๋๋ผ ๋ด์ฉ์ผ๋ก ํ๋ณ |
|
|
197
|
+
| ์ง์ | ๋ณธ๋ฌธ ๋จ๋ฝ | ํญ์ ๊ณต๋ฐฑ, ์ค๋ฐ๊ฟ ์ ์ง |
|
|
198
|
+
| ์ง์ | ์ ๋ชฉ ๊ณ์ธต | ๋ฌธ๋จ ๋ชจ์์ ๊ฐ์ 1~6์์ค โ `#`~`######`, 7์์ค์ `######` |
|
|
199
|
+
| ์ง์ | ํ | GFM ํ (์ฒซ ํ์ด ํค๋), ์
์ ์ฌ๋ฌ ๋จ๋ฝ์ `<br>`, `\|` ์ด์ค์ผ์ดํ, ์ค์ฒฉ ํ๋ ์
ํ
์คํธ๋ก ํํํ |
|
|
200
|
+
| ์ง์ | ์ด๋ฏธ์ง | ๋ณธ๋ฌธ ๊ทธ๋ฆผ์ `<์ด๋ฆ>_images/img_001.png` โฆ ๋ก ์ถ์ถ(์๋ณธ ๋ฐ์ดํธ ๊ทธ๋๋ก)ํ๊ณ ์ ์๋ฆฌ์ `` ๋งํฌ. ๊ฐ์ ๊ทธ๋ฆผ์ ๋ค์ ์ฐธ์กฐํ๋ฉด ํ์ผ 1๊ฐ |
|
|
201
|
+
| ์ง์ | ํ ์นธ ์ ๊ทธ๋ฆผ | ์นธ ์์ `` ๋งํฌ. ๋ฌธ์ ์์๋๋ก ๊ทธ๋ฆผ ๋ฒํธ๋ฅผ ์ด๋ค |
|
|
202
|
+
| ์ง์ | ์บก์
| ํยท๊ทธ๋ฆผ ์บก์
์ ๊ฐ์ฒด ๋ค ๋จ๋ฝ์ผ๋ก ์ถ๋ ฅ |
|
|
203
|
+
| ์ง์ | ๊ธ์์ยท๋ํยท๋ฌถ์ | ์์ชฝ ๊ธ๊ณผ ๊ทธ๋ฆผ์ ๊ฐ์ฒด ์๋ฆฌ์ ์ถ๋ ฅ |
|
|
204
|
+
| ์ง์ | ํ์ดํผ๋งํฌ | `[๊ธ์](์ฃผ์)`. httpยทhttpsยทmailto ๋ง ๋งํฌ๋ก ์ฐ๊ณ ๊ทธ ๋ฐ์ ์ฃผ์๋ ๊ธ์๋ง ๋จ๊ธด๋ค |
|
|
205
|
+
| ์ง์ | ๊ฐ์ฃผยท๋ฏธ์ฃผ | ๋ณธ๋ฌธ์ `[^1]`ยท`[^e1]` ์ฐธ์กฐ, ๋ฌธ์ ๋์ ์ ์ |
|
|
206
|
+
| ์ง์ | ์์ | ํ๊ธ ์์ ์คํฌ๋ฆฝํธ โ LaTeX `$โฆ$` (๋จ๋ฝ์ ์์๋ง ์์ผ๋ฉด `$$โฆ$$`). ๋ณํ์ ์คํจํ๋ฉด ์๋ฌธ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ๋ ์คํฌ์ผ๋ก |
|
|
207
|
+
| ์ง์ | ๋ฒํธยท๊ธ๋จธ๋ฆฌํ ๋ชฉ๋ก | ๊ธ๋จธ๋ฆฌํ๋ `-`, `1.`ยท`1)` ๊ผด ๋ฒํธ๋ Markdown ๋ชฉ๋ก ๋ฌธ๋ฒ, ๊ทธ ๋ฐ์ ๋ฒํธ(`๊ฐ.`ยท`โ ` ๋ฑ)๋ ๋จ๋ฝ ์ ๊ธ์๋ก. ๋ฒํธ๋ ๋ฌธ๋จ ๋ฒํธ ์ ์๋ก ๊ทผ์ฌ ๊ณ์ฐ |
|
|
208
|
+
| ์ง์ | OOXML ์ฐจํธ | ์ฐจํธ์ ์ ์ฅ๋ ๊ฐ์ผ๋ก `์ฐจํธ: ์ ๋ชฉ` ๋จ๋ฝ + ํ |
|
|
209
|
+
| ์ง์ | ๋ฐฐํฌ์ฉ ๋ฌธ์ | ๊ณต๊ฐ๋ ์ ์ฐจ๋ก ๋ณตํธํํด ๋ณํ (์๋ "๋ฐฐํฌ์ฉ ๋ฌธ์ ๊ณ ์ง") |
|
|
210
|
+
| ๋ถ๋ถ | OLE ๊ฐ์ฒด | ์๋ณธ์ด ๊ทธ๋ฆผ(PNGยทJPEGยทGIFยทBMP)์ด๋ฉด ๊ทธ๋ฆผ์ผ๋ก ์ถ์ถ, ๊ทธ ๋ฐ์ ๊ฑด๋๋ ๋ณด๊ณ |
|
|
211
|
+
| ์ ํ | ๋ณํฉ ์
| ์ ์
์ ํ
์คํธ, ๋ฎ์ธ ์นธ์ ๋น ์นธ (GFM ์ด ๋ณํฉ์ ํํํ์ง ๋ชปํจ) |
|
|
212
|
+
| ์ ์ธ | ๋จธ๋ฆฌ๋งยท๊ผฌ๋ฆฌ๋ง | ๋ณธ๋ฌธ ํ๋ฆ์ ๋ฃ์ง ์๊ณ (์ฒญํน ์ก์ ๋ฐฉ์ง) ๊ฑด๋๋์ผ๋ก ๋ณด๊ณ . ์ธ ํฌ๋งท ๋์ผ |
|
|
213
|
+
| ์ ์ธ | ๋ฉ๋ชจยท์จ์ ์ค๋ช
| ์์ฑ์ ์ ๋ณด๊ฐ ์์ ์ ์์ด ๋ด์ฉ์ ์ถ๋ ฅํ์ง ์๊ณ ๊ฐ์๋ง ๋ณด๊ณ |
|
|
214
|
+
| ์ ํ | ์์ | ๊ธ๊ผดยท์์ยท์ ๋ ฌ์ ๋ฒ๋ฆฐ๋ค ("ํ์ง ์๋ ๊ฒ" ์ฐธ์กฐ) |
|
|
215
|
+
| ๊ฑฐ๋ถ | DRM ์ ์ฉ ๋ฌธ์ | `DrmProtectedError`, CLI ์ข
๋ฃ ์ฝ๋ 3, API ์ค๋ฅ ์ฝ๋ `drm` |
|
|
216
|
+
| ๊ฑฐ๋ถ | ๋น๋ฐ๋ฒํธ ๋ฌธ์ | `EncryptedDocumentError`, CLI ์ข
๋ฃ ์ฝ๋ 3, API ์ค๋ฅ ์ฝ๋ `encrypted`. ๋น๋ฐ๋ฒํธ ์
๋ ฅ ๊ธฐ๋ฅ์ ์๋ค |
|
|
217
|
+
| ๋ฏธ์ง์ | HWP 3.0 ์ดํ | HWP ํ์ผ์ด ์๋ ๊ฒ์ผ๋ก ํ์ (์ข
๋ฃ ์ฝ๋ 3) |
|
|
218
|
+
|
|
219
|
+
์์๋ณ ์ํ(์ถ์ถยท๋ถ๋ถยท๋ถ๊ฐยท์ ์ธ)์ ์ด์ , ๋์์ [docs/extraction-limits.md](docs/extraction-limits.md) ์ ์๋ค.
|
|
220
|
+
|
|
221
|
+
### ๊ฑด๋๋ ๋ณด๊ณ
|
|
222
|
+
|
|
223
|
+
๋ณํํ์ง ๋ชปํ ์์๋ ์กฐ์ฉํ ๋ฒ๋ฆฌ์ง ์๊ณ ์ข
๋ฅ๋ณ ๊ฐ์๋ฅผ ์๋ ค ์ค๋ค. ์ข
๋ฅ๋ ๋จธ๋ฆฌ๋งยท๊ผฌ๋ฆฌ๋ง, ๋ฉ๋ชจ, ์จ์ ์ค๋ช
, ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ผ ์ ์๋ ์ฐจํธ, OLE ๊ฐ์ฒด, ๊ธ๋งต์, ์์ ๊ฐ์ฒด, ์ธ๋ถ ์ฐ๊ฒฐ ๊ทธ๋ฆผ, ๋ณธ์ฒด ์๋ ๊ทธ๋ฆผ ์ํ ๊ฐ์ง๋ค.
|
|
224
|
+
|
|
225
|
+
| ์ด๋์ | ๋ชจ์ |
|
|
226
|
+
|---|---|
|
|
227
|
+
| CLI | ์์ฝ ๋ ์ค ` ๊ฑด๋๋: ๋จธ๋ฆฌ๋งยท๊ผฌ๋ฆฌ๋ง 2๊ฐ ยท OLE ๊ฐ์ฒด 1๊ฐ` |
|
|
228
|
+
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ | `ConvertResult.skipped`ยท`MemoryResult.skipped` (์ข
๋ฅ, ๊ฐ์) ํํ |
|
|
229
|
+
| ์น API | ์๋ต JSON ์ `skipped`: `[{"kind": "header_footer", "label": "๋จธ๋ฆฌ๋งยท๊ผฌ๋ฆฌ๋ง", "count": 2}]` (ํญ์ ์์, ์์ผ๋ฉด ๋น ๋ฐฐ์ด) |
|
|
230
|
+
| ์น ํ๋ฉด | ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ "๋ณํํ์ง ๋ชปํ ์์: โฆ" ๊ฒฝ๊ณ |
|
|
231
|
+
|
|
232
|
+
### ๋ฐฐํฌ์ฉ ๋ฌธ์ ๊ณ ์ง
|
|
233
|
+
|
|
234
|
+
๋ฐฐํฌ์ฉ ๋ฌธ์(ํธ์งยท๋ณต์ฌยท์ธ์๋ฅผ ์ ํํด ๋ฐฐํฌํ HWP 5.0 ๋ฌธ์)๋ ๊ธฐ๋ณธ์ผ๋ก ๋ณํํ๋ค. ํธ์งยท๋ณต์ฌ ์ ํ ๋ฌธ์๋ฅผ ํ
์คํธ๋ก ๊บผ๋ด๋ฏ๋ก ์์ฑ์์ ์๋์ ์ฌ์ฉ ๊ถํ์ ํ์ธํ์ธ์. hwp2md ๋ ๊ณต๊ฐ๋ ๋ณตํธํ ์ ์ฐจ๋ง ์ฐ๋ฉฐ, DRM ์ ์ฉ ๋ฌธ์์ ๋น๋ฐ๋ฒํธ ๋ฌธ์๋ ํ์ง ์๊ณ ๊ฑฐ๋ถํ๋ค.
|
|
235
|
+
|
|
236
|
+
## ๊ตฌ์กฐ
|
|
237
|
+
|
|
238
|
+
```mermaid
|
|
239
|
+
flowchart LR
|
|
240
|
+
subgraph entry["์ง์
์ "]
|
|
241
|
+
CLI["CLI<br>hwp2md INPUT -o OUT"]
|
|
242
|
+
LIB["๋ผ์ด๋ธ๋ฌ๋ฆฌ<br>convert / convert_bytes"]
|
|
243
|
+
WEB["์น ์๋ฒ<br>hwp2md serve"]
|
|
244
|
+
PYO["๋ธ๋ผ์ฐ์ Pyodide<br>๊ฐ์ ์์ค๋ฅผ ๋ธ๋ผ์ฐ์ ์์ ์คํ"]
|
|
245
|
+
end
|
|
246
|
+
IN[".hwp / .hwpx / .hml<br>๊ฒฝ๋ก ๋๋ bytes"] --> DET["detect<br>๋ด์ฉ์ผ๋ก ํฌ๋งท ํ๋ณ"]
|
|
247
|
+
CLI --> IN
|
|
248
|
+
LIB --> IN
|
|
249
|
+
WEB --> IN
|
|
250
|
+
PYO --> IN
|
|
251
|
+
DET -->|"OLE"| H5["hwp5 ๋ฆฌ๋<br>๋ ์ฝ๋ ํ์ฑ"]
|
|
252
|
+
DET -->|"zip + mimetype"| HX["hwpx ๋ฆฌ๋<br>XML ํ์ฑ"]
|
|
253
|
+
DET -->|"๋ฃจํธ HWPML"| HM["hwpml ๋ฆฌ๋<br>XML ํ์ฑ"]
|
|
254
|
+
H5 --> BL["Block ๋ฆฌ์คํธ<br>Paragraph / Table / Image / Note"]
|
|
255
|
+
HX --> BL
|
|
256
|
+
HM --> BL
|
|
257
|
+
BL --> RD["render<br>Markdown"]
|
|
258
|
+
RD --> OUT["๋ฌธ์.md + ๋ฌธ์_images/<br>(CLI) ๋๋ ๋ฉ๋ชจ๋ฆฌ ๊ฒฐ๊ณผ(์นยทAPI)"]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
์ธ ํฌ๋งท์ ๋ฆฌ๋๋ ๊ฐ์ ๋ธ๋ก ๋ชจ๋ธ์ ๋๋ ค์ฃผ๊ณ , ๋ ๋๋ฌ๋ ํฌ๋งท์ ๋ชจ๋ฅธ๋ค. ๊ทธ๋์ CLIยท์น ์๋ฒยท๋ธ๋ผ์ฐ์ ๋ชจ๋๊ฐ ๊ฐ์ Markdown ์ ๋ธ๋ค. ๋ชจ๋๋ณ ์ญํ , ์น ์๋ํฌ์ธํธ, ๋ธ๋ผ์ฐ์ ๋ชจ๋ ์ํ์ค, ํ์ฅ ๋ฐฉ๋ฒ์ [docs/system-overview.md](docs/system-overview.md) ์ ์๋ค.
|
|
262
|
+
|
|
263
|
+
## ๊ฐ์ธ์ ๋ณดยท๋ณด์ ์ค๊ณ
|
|
264
|
+
|
|
265
|
+
- **์
๋ก๋๋ ๋์คํฌ์ ์ฐ์ง ์๋๋ค.** ์น ์๋ฒ๋ ์์ฒญ ๋ณธ๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ์์ ๋ฐ๋ก ๋ณํํ๋ค(`convert_bytes`). ์์ ํ์ผ๋ ๋ง๋ค์ง ์๋๋ค. E2E `test_no_files_written` ์ด ๋ณํยทzipยท์ค๋ฅ ์์ฒญ ๋ค ์๋ฒ์ ์์
ํด๋์ ์์ ํด๋๊ฐ ๋น์ด ์๋์ง ํ์ธํ๋ค.
|
|
266
|
+
- **๋ธ๋ผ์ฐ์ ๋ชจ๋๋ ํ์ผ ๋ฐ์ดํธ๋ฅผ ์๋ฒ๋ก ๋ณด๋ด์ง ์๋๋ค.** ๋คํธ์ํฌ ์์ฒญ์ Pyodideยทlxml ๋ค์ด๋ก๋(jsDelivr CDN)์ hwp2md ์์ค zip(`GET /pkg/hwp2md.zip`)๋ฟ์ด๋ค. E2E `test_browser_mode_sends_no_file` ์ด ๋ณํ ์ค GET ์ด์ธ ์์ฒญ 0๊ฑด, `/api/` ์์ฒญ 0๊ฑด์ ํ์ธํ๋ค.
|
|
267
|
+
- **๋ฏธ๋ฆฌ๋ณด๊ธฐ XSS ๋ฐฉ์ด.** ๋ฌธ์ ๋ด์ฉ์ ์ธ๋ถ ์
๋ ฅ์ผ๋ก ๋ณธ๋ค. ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ ๋๋ฌ๋ ์์ HTML ์ค ํ ์
์ค๋ฐ๊ฟ์ฉ `<br>` ๋ง ์ด๋ฆฌ๊ณ ๋๋จธ์ง๋ ์ด์ค์ผ์ดํํ๋ค. ๋งํฌ ์ฃผ์๋ httpยทhttpsยทmailto ์ ์๋ ๊ฒฝ๋ก๋ง, ์ด๋ฏธ์ง ์ฃผ์๋ ์๋ ๊ฒฝ๋ก์ `data:image/*;base64` ๋ง ํ์ฉํ๋ค.
|
|
268
|
+
- **XMLยทzip ๋ฐฉ์ด.** HWPX XML ์ ์ธ๋ถ ์ํฐํฐยท๋คํธ์ํฌ๋ฅผ ๋ ํ์๋ก ์ฝ๊ณ , 50MB ๋ฅผ ๋๋ ์น์
์ ๊ฑฐ๋ถํ๋ค. ์น ์
๋ ฅ์ 50MB ์ํ์ด๋ฉฐ `Content-Length` ๊ฐ ๋์ผ๋ฉด ๋ณธ๋ฌธ์ ์ฝ๊ธฐ ์ ์ 413 ์ผ๋ก ๊ฑฐ์ ํ๋ค.
|
|
269
|
+
- **๋ก์ปฌ ์ ์ฉ ๋๊ตฌ๋ค.** ๊ธฐ๋ณธ ๋ฐ์ธ๋๋ `localhost` ์ด๊ณ ์ธ์ฆ์ด ์๋ค. `--host` ๋ก ์ธ๋ถ์ ์ด ์๋ ์์ง๋ง ์ธ๋ถ ๊ณต๊ฐ์ฉ์ผ๋ก ์ค๊ณํ์ง ์์๋ค.
|
|
270
|
+
|
|
271
|
+
## ํ์งยทํ
์คํธ
|
|
272
|
+
|
|
273
|
+
| ์ข
๋ฅ | ์์น | ์ | ๋ฌด์์ ๋ณด๋ |
|
|
274
|
+
|---|---|---|---|
|
|
275
|
+
| ๋จ์ | `tests/unit` | 813 passed (`uv run pytest tests/unit -q`, 2026-10-23 ์ง์ ์คํ) | ๋ ์ฝ๋ยทXML ํ์, ์ ๋ชฉ ํ์ , ํ ์กฐ๋ฆฝ, ์ด๋ฏธ์ง ์ถ์ถ, ๋ ๋๋ฌ, ํฌ๋งท ํ๋ณ, ๋ฉ๋ชจ๋ฆฌ API, ์น ์ฑ(TestClient) |
|
|
276
|
+
| E2E | `tests/e2e` | 120 passed (`uv run pytest tests/e2e -q`, 2026-10-23 ์ง์ ์คํ, ์ค์ ์ํ ์์๋ณ 18๊ฐ ํฌํจ) | ์ค์ ์ฝ์ ์คํฌ๋ฆฝํธ ์คํ, ์ค์ ์๋ฒ + Playwright(Chromium) ํ๋ฉด |
|
|
277
|
+
|
|
278
|
+
E2E ๊ฐ ๊ฒ์ฆํ๋ ํต์ฌ:
|
|
279
|
+
- ์๋ฒ ๋ชจ๋ ยท ๋ธ๋ผ์ฐ์ ๋ชจ๋ ยท CLI ์ Markdown ์ด ๊ฐ๋ค (`test_modes_produce_identical_markdown`, `test_convert_matches_cli`).
|
|
280
|
+
- zip ๋ค์ด๋ก๋ ๋ด์ฉ์ด CLI ์ถ๋ ฅ๊ณผ ๊ฐ๋ค.
|
|
281
|
+
- ๋ธ๋ผ์ฐ์ ๋ชจ๋์์ ํ์ผ์ด ์๋ฒ๋ก ๊ฐ์ง ์๋๋ค, ์๋ฒ ๋ชจ๋์์ ์
๋ก๋๊ฐ ๋์คํฌ์ ๋จ์ง ์๋๋ค.
|
|
282
|
+
- ์๋ชป๋ ์
๋ ฅ(HWP ์๋ยท๋น ํ์ผยท50MB ์ด๊ณผ)์ API ๊ฐ ๊ฐ๊ฐ 415ยท400ยท413 ์ผ๋ก ๊ฑฐ์ ํ๊ณ , HWP ๊ฐ ์๋ ํ์ผ์ ์ค๋ฅ ๋ฌธ๊ตฌ๋ ๋ ๋ชจ๋์์ ๊ฐ๋ค.
|
|
283
|
+
- ํฌํธ ์ถฉ๋ยทweb extra ๋ฏธ์ค์น ์ ์๋ด์ ์ข
๋ฃ ์ฝ๋.
|
|
284
|
+
|
|
285
|
+
ํ
์คํธ ๋ฌธ์๋ `tests/synth_hwp5.py`ยท`tests/synth_hwpx.py` ๊ฐ ๋ช
์ธ ๊ธฐ์ค์ผ๋ก ์ฝ๋์์ ๋ง๋ ๋ค(์ ๋ชฉยทํยท๋ณํฉ ์
ยท์ด๋ฏธ์งยท์ํธํ ํ๋๊ทธ ๋ฑ ๋ณํ์ ๋ฐ์ดํธ ๋จ์๋ก ํต์ ). ์ค์ ๊ณต๊ฐ ์ํ์ ๋ผ์ด์ ์ค ํ์ธ ์ ์ด๋ผ ์ ์ฅ์์ ๋ฃ์ง ์์๊ณ , `tests/fixtures/` ์ ๋ฃ์ผ๋ฉด E2E ๊ฐ ์๋์ผ๋ก ํจ๊ป ๋๋ฆฐ๋ค.
|
|
286
|
+
|
|
287
|
+
๊ฐ๋ฐ ๋ช
๋ น:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
uv sync && uv run playwright install chromium # ๊ฐ๋ฐ ํ๊ฒฝ (E2E ์ฉ ๋ธ๋ผ์ฐ์ ํฌํจ)
|
|
291
|
+
uv run pytest tests/unit -q # ๋จ์
|
|
292
|
+
uv run pytest tests/e2e -q # E2E (๋ธ๋ผ์ฐ์ ๋ชจ๋ ์ผ์ด์ค๋ ์ธํฐ๋ท ํ์)
|
|
293
|
+
uv run ruff check src tests && uv run ruff format --check src tests
|
|
294
|
+
uv run mypy src # strict
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## ํ๊ณ์ ์๋ ค์ง ์ด์
|
|
298
|
+
|
|
299
|
+
- ๋ธ๋ผ์ฐ์ ๋ชจ๋ ์ฒซ ๋ก๋ฉ์ด ์ฝ 1๋ถ ๊ฑธ๋ฆฌ๊ณ ์ธํฐ๋ท(jsDelivr)์ด ํ์ํ๋ค. ์คํ๋ผ์ธ ๋ฒ๋ค์ ์๋ค.
|
|
300
|
+
- ๊ณต๊ณต๊ธฐ๊ด ์ํ 20๊ฐ ๋ณํ ํ์ง ๋น๊ตํ๋ ์์ง ์์ฑํ์ง ์์๋ค.
|
|
301
|
+
- CI ๋ Python 3.11ยท3.13 ์์ ๊ฒ์ฆํ๋ค. 3.14 ์์๋ ์์๋ zip ์ ์ค๋ฅ ๋ฌธ๊ตฌ๊ฐ ๋ค๋ฅด๊ฒ ๋์ค๋ ์๋ ค์ง ์ฐจ์ด๊ฐ ์๋ค.
|
|
302
|
+
- ๊ธฐ๋ณธ ํฌํธ 8770 ์ด ๋ค๋ฅธ ํ๋ก๊ทธ๋จ๊ณผ ๊ฒน์น๋ฉด `--port` ๋ก ๋ฐ๊พผ๋ค (์๋์ผ๋ก ๋ค์ ํฌํธ๋ฅผ ์ฐพ์ง ์๋๋ค).
|
|
303
|
+
- ์ด๋ฏธ์ง๊ฐ ์๋ ์ค์ HWP 5.0 ์ํ์ด ์์ด, HWP 5.0 ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ ํฉ์ฑ ๋ฌธ์์ ๊ณต๊ฐ ์คํ ๊ต์ฐจ ํ์ธ์ผ๋ก๋ง ๊ฒ์ฆํ๋ค.
|
|
304
|
+
- ์ด๋ฏธ์ง ํฌ๋งท์ ๋ณํํ์ง ์๋๋ค. WMF/EMF ๊ฐ์ ์๋ณธ์ ๊ทธ๋๋ก ์ ์ฅ๋์ด ๋ธ๋ผ์ฐ์ ์์ ์ ๋ณด์ผ ์ ์๋ค.
|
|
305
|
+
- ๊ตฌํ ์ฐจํธ(์ฐจํธ ๊ฐ์ด OOXML ๋ก ์ ์ฅ๋์ง ์์ ๊ฒ)๋ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ด์ง ๋ชปํ๊ณ ๊ฑด๋๋์ผ๋ก ๋ณด๊ณ ํ๋ค.
|
|
306
|
+
- ์ชฝ ๋ฒํธยท๋ ์ง ๊ฐ์ ํ๋ ๊ฐ์ ๋ค์ ๊ณ์ฐํ์ง ์๋๋ค. ๋ชฉ๋ก ๋ฒํธ๋ ๊ทผ์ฌ๊ฐ์ด๋ค.
|
|
307
|
+
- ๋จ๋ฝ ์ค๊ฐ์ ๋์ธ ๊ทธ๋ฆผ์ ์์น๊ฐ HWP 5.0 ๊ณผ HWPX ์์ ๋ค๋ฅผ ์ ์๋ค.
|
|
308
|
+
- ์์๋ณ ์ ์ฒด ๋ชฉ๋ก๊ณผ ๋์์ [docs/extraction-limits.md](docs/extraction-limits.md) ์ ์๋ค.
|
|
309
|
+
|
|
310
|
+
## ๋ก๋๋งต
|
|
311
|
+
|
|
312
|
+
- [x] ๊ฐ์ฃผยท๋ฏธ์ฃผ โ Markdown ํ๋จ ์ฐธ์กฐ(`[^1]`)
|
|
313
|
+
- [ ] RAG ์ต์
: `--split section` (์ ๋ชฉ ๋จ์ ๋ถํ ), `--meta` (๋ชฉ์ฐจยทํ ์ยท์ด๋ฏธ์ง ๊ฒฝ๋กยท์๋ณธ ํด์ JSON)
|
|
314
|
+
- [ ] ํด๋ ์ผ๊ด ๋ณํ
|
|
315
|
+
- [x] ๋ฐฐํฌ์ฉ ๋ฌธ์ ์ง์
|
|
316
|
+
- [ ] ๋ณํฉ ์
๊ฐ์ (`--table html` ๋ก rowspan/colspan ๋ณด์กด)
|
|
317
|
+
- [ ] ๊ณต๊ณต๊ธฐ๊ด ์ํ 20๊ฐ ํ์ง ๋น๊ตํ
|
|
318
|
+
- [x] PyPI ๋ฐฐํฌ(hwp2markdown) + GitHub Actions CI
|
|
319
|
+
|
|
320
|
+
## ๋ฌธ์
|
|
321
|
+
|
|
322
|
+
- [docs/vision.md](docs/vision.md) โ ์์ฑ๋ ๋ชจ์ต (์ฌ์ฉ์ ์๋๋ฆฌ์ค)
|
|
323
|
+
- [docs/system-overview.md](docs/system-overview.md) โ ๋ด๋ถ ๊ตฌ์กฐ (๋ชจ๋ยท๋ฐ์ดํฐ ํ๋ฆยท์นยท๋ณด์ยทํ
์คํธยทํ์ฅ)
|
|
324
|
+
- [docs/decisions.md](docs/decisions.md) โ ๊ฒฐ์ ๊ธฐ๋ก (๋ ์ง์ ์ด์ )
|
|
325
|
+
- [docs/releasing.md](docs/releasing.md) โ ๋ฆด๋ฆฌ์ค ์ ์ฐจ (PyPI ๊ฒ์ ์ค์ ยทํ๊ทธยท์คํจ ๋์)
|
|
326
|
+
- [docs/extraction-limits.md](docs/extraction-limits.md) โ ์ถ์ถ ํ๊ณ (์์๋ณ ์ํยท์ด์ ยท๋์)
|
|
327
|
+
|
|
328
|
+
## ํ์ง ์๋ ๊ฒ
|
|
329
|
+
|
|
330
|
+
- ์ญ๋ณํ (md โ hwp)
|
|
331
|
+
- ์์ ์๋ฒฝ ์ฌํ (๊ธ๊ผดยท์์)
|
|
332
|
+
|
|
333
|
+
## ๋ผ์ด์ ์ค
|
|
334
|
+
|
|
335
|
+
- hwp2md ๋ [MIT ๋ผ์ด์ ์ค](LICENSE)๋ก ๋ฐฐํฌํ๋ค.
|
|
336
|
+
- `src/hwp2md/equation/eqedit.py`ยท`tokens.py` ๋ python-hwpx ์์ ๊ฐ์ ธ์จ ์ฝ๋๋ก Apache License 2.0 ์ ๋ฐ๋ฅธ๋ค. ์ถ์ฒยท์ปค๋ฐยท๋ณ๊ฒฝ ์ฌ์ค์ [NOTICE](NOTICE) ์ ์๋ค.
|
|
337
|
+
- ๋ณธ ์ ํ์ ํ๊ธ๊ณผ์ปดํจํฐ์ ํ/๊ธ ๋ฌธ์ ํ์ผ(.hwp) ๊ณต๊ฐ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ๊ฐ๋ฐํ์์ต๋๋ค.
|