├── .github └── workflows │ └── deploy.yaml ├── .gitignore ├── .virtual_documents └── nbs │ └── 02_api │ └── 00_core.ipynb ├── .vscode └── settings.json ├── HwpApi ├── FilePathCheckerModuleExample.dll ├── __init__.py ├── _modidx.py ├── actions.py ├── constants.py ├── core.py ├── dataclasses.py └── functions.py ├── LICENSE ├── MANIFEST.in ├── README.md ├── _proc ├── 01_tutorials │ ├── 01_app_basics.ipynb │ └── 02_find_and_replace.ipynb ├── 02_api │ ├── 00_core.ipynb │ ├── 01_actions.ipynb │ ├── 02_functions.ipynb │ ├── 03_dataclasses.ipynb │ ├── 04_constants.ipynb │ └── test.pdf ├── _quarto.yml └── index.ipynb ├── action_table └── Action Table_modified.htm ├── docs ├── 01_tutorials │ ├── find_replace.html │ ├── img │ │ ├── 분양가상한제1.png │ │ ├── 분양가상한제2.png │ │ ├── 시범사업지.png │ │ ├── 시범사업지_변경.png │ │ ├── 주거안정.png │ │ └── 주거안정_변경.png │ └── tutorial.html ├── 02_api │ ├── actions.html │ ├── constants.html │ ├── core.html │ ├── dataclasses.html │ └── functions.html ├── index.html ├── robots.txt ├── search.json ├── site_libs │ ├── bootstrap │ │ ├── bootstrap-icons.css │ │ ├── bootstrap-icons.woff │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.js │ ├── clipboard │ │ └── clipboard.min.js │ ├── quarto-html │ │ ├── anchor.min.js │ │ ├── popper.min.js │ │ ├── quarto-syntax-highlighting-dark.css │ │ ├── quarto.js │ │ ├── tippy.css │ │ └── tippy.umd.min.js │ ├── quarto-nav │ │ ├── headroom.min.js │ │ └── quarto-nav.js │ └── quarto-search │ │ ├── autocomplete.umd.js │ │ ├── fuse.min.js │ │ └── quarto-search.js ├── sitemap.xml └── styles.css ├── nbs ├── 01_tutorials │ ├── 01_app_basics.ipynb │ ├── 02_find_and_replace.ipynb │ ├── hwps │ │ └── 220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp │ └── img │ │ ├── 분양가상한제1.png │ │ ├── 분양가상한제2.png │ │ ├── 시범사업지.png │ │ ├── 시범사업지_변경.png │ │ ├── 주거안정.png │ │ └── 주거안정_변경.png ├── 02_api │ ├── 00_core.ipynb │ ├── 01_actions.ipynb │ ├── 02_functions.ipynb │ ├── 03_dataclasses.ipynb │ ├── 04_constants.ipynb │ ├── test.pdf │ └── test │ │ └── 공문양식.hwp ├── __init__.py ├── _quarto.yml ├── index.ipynb ├── nbdev.yml ├── sidebar.yml └── styles.css ├── settings.ini └── setup.py /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | permissions: 4 | contents: write 5 | pages: write 6 | 7 | on: 8 | push: 9 | branches: [ "main", "master" ] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: windows-latest 15 | steps: [uses: fastai/workflows/quarto-ghp@master] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | # 133 | todo.md 134 | _docs 135 | _proc -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "julia.environmentPath": "c:\\Users\\freed\\Documents\\python_projects\\HwpApi" 3 | } -------------------------------------------------------------------------------- /HwpApi/FilePathCheckerModuleExample.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/HwpApi/FilePathCheckerModuleExample.dll -------------------------------------------------------------------------------- /HwpApi/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.2.2" 2 | -------------------------------------------------------------------------------- /HwpApi/constants.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_api/04_constants.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['char_fields', 'para_fields', 'korean_fonts', 'english_fonts', 'chinese_fonts', 'japanese_fonts', 'other_fonts', 5 | 'symbol_fonts', 'user_fonts'] 6 | 7 | # %% ../nbs/02_api/04_constants.ipynb 3 8 | # constants 9 | char_fields = [ 10 | "Bold", 11 | "DiacSymMark", 12 | "Emboss", 13 | "Engrave", 14 | "FaceNameHangul", 15 | "FaceNameHanja", 16 | "FaceNameJapanese", 17 | "FaceNameLatin", 18 | "FaceNameOther", 19 | "FaceNameSymbol", 20 | "FaceNameUser", 21 | "FontTypeHangul", 22 | "FontTypeHanja", 23 | "FontTypeJapanese", 24 | "FontTypeLatin", 25 | "FontTypeOther", 26 | "FontTypeSymbol", 27 | "FontTypeUser", 28 | "Height", 29 | "Italic", 30 | "OffsetHangul", 31 | "OffsetHanja", 32 | "OffsetJapanese", 33 | "OffsetLatin", 34 | "OffsetOther", 35 | "OffsetSymbol", 36 | "OffsetUser", 37 | "OutLineType", 38 | "RatioHangul", 39 | "RatioHanja", 40 | "RatioJapanese", 41 | "RatioLatin", 42 | "RatioOther", 43 | "RatioSymbol", 44 | "RatioUser", 45 | "ShadeColor", 46 | "ShadowColor", 47 | "ShadowOffsetX", 48 | "ShadowOffsetY", 49 | "ShadowType", 50 | "SizeHangul", 51 | "SizeHanja", 52 | "SizeJapanese", 53 | "SizeLatin", 54 | "SizeOther", 55 | "SizeSymbol", 56 | "SizeUser", 57 | "SmallCaps", 58 | "SpacingHangul", 59 | "SpacingHanja", 60 | "SpacingJapanese", 61 | "SpacingLatin", 62 | "SpacingOther", 63 | "SpacingSymbol", 64 | "SpacingUser", 65 | "StrikeOutColor", 66 | "StrikeOutShape", 67 | "StrikeOutType", 68 | "SubScript", 69 | "SuperScript", 70 | "TextColor", 71 | "UnderlineColor", 72 | "UnderlineShape", 73 | "UnderlineType", 74 | "UseFontSpace", 75 | "UseKerning", 76 | ] 77 | 78 | para_fields = [ 79 | "AlignType", 80 | "AutoSpaceEAsianEng", 81 | "AutoSpaceEAsianNum", 82 | "BorderConnect", 83 | "BorderOffsetBottom", 84 | "BorderOffsetLeft", 85 | "BorderOffsetRight", 86 | "BorderOffsetTop", 87 | "BorderText", 88 | "BreakLatinWord", 89 | "BreakNonLatinWord", 90 | "Checked", 91 | "Condense", 92 | "FontLineHeight", 93 | "HeadingType", 94 | "Indentation", 95 | "KeepLinesTogether", 96 | "KeepWithNext", 97 | "LeftMargin", 98 | "Level", 99 | "LineSpacing", 100 | "LineSpacingType", 101 | "LineWrap", 102 | "NextSpacing", 103 | "PagebreakBefore", 104 | "PrevSpacing", 105 | "RightMargin", 106 | "SnapToGrid", 107 | "SuppressLineNum", 108 | "TailType", 109 | "TextAlignment", 110 | "WidowOrphan", 111 | ] 112 | 113 | # %% ../nbs/02_api/04_constants.ipynb 4 114 | korean_fonts = [ 115 | "명조", 116 | "고딕", 117 | "샘물", 118 | "필기", 119 | "시스템", 120 | "옛한글", 121 | "가는공한", 122 | "중간공한", 123 | "굵은공한", 124 | "가는한", 125 | "중간한", 126 | "굵은한", 127 | "펜흘림", 128 | "복숭아", 129 | "옥수수", 130 | "오이", 131 | "가지", 132 | "강낭콩", 133 | "딸기", 134 | "타이프", 135 | "HY 둥근고딕", 136 | "한양신명조", 137 | "한양견명조", 138 | "한양중고딕", 139 | "한양견고딕", 140 | "한양그래픽", 141 | "한양궁서", 142 | "휴먼명조", 143 | "휴먼고딕", 144 | "가는안상수체", 145 | "중간안상수체", 146 | "굵은안상수체", 147 | "휴먼가는샘체", 148 | "휴먼중간샘체", 149 | "휴먼굵은샘체", 150 | "휴먼가는팸체", 151 | "휴먼중간팸체", 152 | "휴먼굵은팸체", 153 | "휴먼옛체", 154 | "문화바탕", 155 | "문화바탕제목", 156 | "문화돋움", 157 | "문화돋움제목", 158 | "문화쓰기", 159 | "문화쓰기흘림", 160 | "태 나무", 161 | "태 헤드라인 D", 162 | "태 가는 헤드라인 D", 163 | "태 헤드라인 T", 164 | "태 가는 헤드라인 T", 165 | "양재 다운명조 M", 166 | "양재 본목각 M", 167 | "양재 소슬", 168 | "양재 튼튼 B", 169 | "양재 둘기", 170 | "양재 참숯 B", 171 | "양재 매화", 172 | "양재 샤넬", 173 | "양재 와당", 174 | "양재 이니셜", 175 | "신명 세명조", 176 | "신명 신명조", 177 | "신명 신신명조", 178 | "신명 중명조", 179 | "신명 태명조", 180 | "신명 견명조", 181 | "신명 신문명조", 182 | "신명 순명조", 183 | "신명 세고딕", 184 | "신명 중고딕", 185 | "신명 태고딕", 186 | "신명 견고딕", 187 | "신명 세나루", 188 | "신명 디나루", 189 | "신명 신그래픽", 190 | "신명 태그래픽", 191 | "신명 궁서", 192 | "#세명조", 193 | "#신명조", 194 | "#중명조", 195 | "#신중명조", 196 | "#화명조 A", 197 | "#화명조 B", 198 | "#태명조", 199 | "#신태명조", 200 | "#태신명조", 201 | "#견명조", 202 | "#신문명조", 203 | "#신문태명", 204 | "#신문견명", 205 | "#세고딕", 206 | "#신세고딕", 207 | "#중고딕", 208 | "#태고딕", 209 | "#견고딕", 210 | "#신문고딕", 211 | "#신문태고", 212 | "#신문견고", 213 | "#세나루", 214 | "#신세나루", 215 | "#디나루", 216 | "#신디나루", 217 | "#그래픽", 218 | "#신그래픽", 219 | "#태그래픽", 220 | "#궁서", 221 | "#공작", 222 | "#수암 A", 223 | "#수암 B", 224 | "#빅", 225 | ] 226 | 227 | # %% ../nbs/02_api/04_constants.ipynb 5 228 | english_fonts = [ 229 | "명조", 230 | "고딕", 231 | "산세리프", 232 | "필기", 233 | "시스템", 234 | "가는공한", 235 | "중간공한", 236 | "굵은공한", 237 | "가는한", 238 | "중간한", 239 | "굵은한", 240 | "펜흘림", 241 | "옥수수", 242 | "오이", 243 | "타이프", 244 | "수식", 245 | "HY 둥근고딕", 246 | "한양신명조", 247 | "한양견명조", 248 | "한양중고딕", 249 | "한양견고딕", 250 | "한양그래픽", 251 | "한양궁서", 252 | "HCI Tulip", 253 | "HCI Hollyhock", 254 | "HCI Columbine", 255 | "HCI Acacia", 256 | "HCI Bellflower", 257 | "HCI Hollyhock Narrow", 258 | "HCI Morning Glory", 259 | "HCI Poppy", 260 | "HCI Centaurea", 261 | "가는안상수체영문", 262 | "중간안상수체영문", 263 | "굵은안상수체영문", 264 | "태 나무", 265 | "양재 다운명조 M", 266 | "양재 본목각 M", 267 | "양재 소슬", 268 | "양재 튼튼 B", 269 | "양재 둘기", 270 | "양재 참숯 B", 271 | "양재 매화", 272 | "양재 샤넬", 273 | "양재 와당", 274 | "양재 이니셜", 275 | "신명 세명조", 276 | "신명 신명조", 277 | "신명 신신명조", 278 | "신명 중명조", 279 | "신명 태명조", 280 | "신명 견명조", 281 | "신명 신문명조", 282 | "신명 순명조", 283 | "신명 세고딕", 284 | "신명 중고딕", 285 | "신명 태고딕", 286 | "신명 견고딕", 287 | "신명 신문고딕", 288 | "신명 세나루", 289 | "신명 디나루", 290 | "신명 신그래픽", 291 | "신명 태그래픽", 292 | "신명 궁서", 293 | "#세명조", 294 | "#신명조", 295 | "#중명조", 296 | "#신중명조", 297 | "#화명조 A", 298 | "#화명조 B", 299 | "#태명조", 300 | "#신태명조", 301 | "#태신명조", 302 | "#견명조", 303 | "#신문명조", 304 | "#신문태명", 305 | "#신문견명", 306 | "#세고딕", 307 | "#신세고딕", 308 | "#중고딕", 309 | "#태고딕", 310 | "#견고딕", 311 | "#신문고딕", 312 | "#신문태고", 313 | "#신문견고", 314 | "#세나루", 315 | "#신세나루", 316 | "#디나루", 317 | "#신디나루", 318 | "#그래픽", 319 | "#신그래픽", 320 | "#태그래픽", 321 | "#궁서", 322 | "#필기", 323 | "#공작", 324 | "#수암 A", 325 | "#수암 B", 326 | "#헤드라인 A", 327 | "#헤드라인 B", 328 | "#빅", 329 | "AmeriGarmnd BT", 330 | "Baskerville BT", 331 | "Bodoni Bd BT", 332 | "Bodoni Bk BT", 333 | "CentSchbook BT", 334 | "Courier10 BT", 335 | "Courier10 BT", 336 | "GoudyOlSt BT", 337 | "OCR-A BT", 338 | "OCR-B-10 BT", 339 | "Orator10 BT", 340 | "Swis721 BT", 341 | "BernhardFashion BT", 342 | "Blippo Blk BT", 343 | "BroadwayEngraved BT", 344 | "BrushScript BT", 345 | "Cooper Blk BT", 346 | "CommercialScript BT", 347 | "DomCasual BT", 348 | "Freehand591 BT", 349 | "FuturaBlack BT", 350 | "Hobo BT", 351 | "Liberty BT", 352 | "MurrayHill Bd BT", 353 | "Newtext Bk BT", 354 | "Orbit-B BT", 355 | "ParkAvenue BT", 356 | "Stencil BT", 357 | ] 358 | 359 | # %% ../nbs/02_api/04_constants.ipynb 6 360 | chinese_fonts = [ 361 | "명조", 362 | "시스템", 363 | "시스템 약자", 364 | "시스템 간자", 365 | "신명조 약자", 366 | "신명조 간자", 367 | "중고딕 약자", 368 | "중고딕 간자", 369 | "해서 약자", 370 | "해서 간자", 371 | "한양신명조", 372 | "한양중고딕", 373 | "신명 세명조", 374 | "신명 중명조", 375 | "신명 태명조", 376 | "신명 견명조", 377 | "신명 신문명조", 378 | "신명 세고딕", 379 | "신명 중고딕", 380 | "신명 태고딕", 381 | "신명 견고딕", 382 | "신명 궁서", 383 | "#신명조", 384 | "#중명조", 385 | "#태명조", 386 | "#견명조", 387 | "#신문명조", 388 | "#신문견명", 389 | "#신세고딕", 390 | "#중고딕", 391 | "#태고딕", 392 | "#견고딕", 393 | "#신문고딕", 394 | "#신문견고", 395 | "#신세나루", 396 | "#신디나루", 397 | "#궁서", 398 | ] 399 | 400 | # %% ../nbs/02_api/04_constants.ipynb 7 401 | japanese_fonts = [ 402 | "명조", 403 | "고딕", 404 | "시스템", 405 | "HY 둥근고딕", 406 | "한양신명조", 407 | "한양중고딕", 408 | "신명 신명조", 409 | "신명 태명조", 410 | "신명 견명조", 411 | "신명 중고딕", 412 | "신명 태고딕", 413 | "#세명조", 414 | "#태명조", 415 | "#세고딕", 416 | "#중고딕", 417 | "#태고딕", 418 | "한양신명조 V", 419 | "한양중고딕 V", 420 | "신명 신명조 V", 421 | "신명 태명조 V", 422 | "신명 견명조 V", 423 | "신명 중고딕 V", 424 | "신명 태고딕 V", 425 | "#세명조 V", 426 | "#태명조 V", 427 | "#세고딕 V", 428 | "#중고딕 V", 429 | "#태고딕 V", 430 | ] 431 | 432 | # %% ../nbs/02_api/04_constants.ipynb 8 433 | other_fonts = ["명조", "수식", "한양신명조"] 434 | 435 | # %% ../nbs/02_api/04_constants.ipynb 9 436 | symbol_fonts = [ 437 | "명조", 438 | "시스템", 439 | "수식", 440 | "HY 둥근고딕", 441 | "한양신명조", 442 | "한양중고딕", 443 | "신명 견명조", 444 | "신명 견고딕", 445 | "신명 태그래픽", 446 | "#세명조", 447 | "#신명조", 448 | "#중명조", 449 | "#태명조", 450 | "#신태명조", 451 | "#태신명조", 452 | "#견명조", 453 | "#신문명조", 454 | "#세고딕", 455 | "#신세고딕", 456 | "#중고딕", 457 | "#태고딕", 458 | "#견고딕", 459 | "#신문고딕", 460 | "#세나루", 461 | "#신세나루", 462 | "#디나루", 463 | "#신디나루", 464 | "#그래픽", 465 | "#신그래픽", 466 | "#태그래픽", 467 | "#궁서", 468 | "#필기", 469 | "#공작", 470 | "#수암 A", 471 | "#빅", 472 | ] 473 | 474 | # %% ../nbs/02_api/04_constants.ipynb 10 475 | user_fonts = ["명조", "한글 풀어쓰기"] 476 | -------------------------------------------------------------------------------- /HwpApi/functions.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_api/02_functions.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['get_font_name', 'dispatch', 'get_hwp_objects', 'get_absolute_path', 'get_dll_path', 'add_dll_to_registry', 5 | 'get_registry_value', 'check_dll', 'get_value', 'get_key', 'convert2int', 'set_pset', 'get_charshape_pset', 6 | 'set_charshape_pset', 'get_parashape_pset', 'set_parashape_pset', 'hex_to_rgb', 'get_rgb_tuple', 7 | 'convert_to_hwp_color', 'convert_hwp_color_to_hex', 'mili2unit', 'unit2mili', 'point2unit', 'unit2point', 8 | 'block_input'] 9 | 10 | # %% ../nbs/02_api/02_functions.ipynb 3 11 | import importlib.resources 12 | import os 13 | import shutil 14 | import winreg 15 | from pathlib import Path 16 | import re 17 | import win32com.client as win32 18 | import pythoncom 19 | from win32com.client import Dispatch 20 | from win32com import client 21 | 22 | from .constants import char_fields, para_fields 23 | 24 | # %% ../nbs/02_api/02_functions.ipynb 4 25 | def get_font_name(text): 26 | m = re.search("(^.+?)\s[A-Z0-9]+\.HFT", text) 27 | return m.group(1) if m else None 28 | 29 | # %% ../nbs/02_api/02_functions.ipynb 5 30 | def dispatch(app_name): 31 | """캐시가 충돌하는 문제를 해결하기 위해 실행합니다. 에러가 발생할 경우 기존 캐시를 삭제하고 다시 불러옵니다.""" 32 | try: 33 | from win32com import client 34 | 35 | app = client.gencache.EnsureDispatch(app_name) 36 | except AttributeError: 37 | # Corner case dependencies. 38 | import os 39 | import re 40 | import shutil 41 | import sys 42 | 43 | # Remove cache and try again. 44 | MODULE_LIST = [m.__name__ for m in sys.modules.values()] 45 | for module in MODULE_LIST: 46 | if re.match(r"win32com\.gen_py\..+", module): 47 | del sys.modules[module] 48 | shutil.rmtree(os.path.join(os.environ.get("LOCALAPPDATA"), "Temp", "gen_py")) 49 | from win32com import client 50 | 51 | app = client.gencache.EnsureDispatch(app_name) 52 | return app 53 | 54 | # %% ../nbs/02_api/02_functions.ipynb 6 55 | def get_hwp_objects(): 56 | context = pythoncom.CreateBindCtx(0) 57 | 58 | # 현재 실행중인 프로세스를 가져옵니다. 59 | running_coms = pythoncom.GetRunningObjectTable() 60 | monikers = running_coms.EnumRunning() 61 | 62 | hwp_objects = [] 63 | for moniker in monikers: 64 | name = moniker.GetDisplayName(context,moniker); 65 | # moniker의 DisplayName을 통해 한글을 가져옵니다 66 | # 한글의 경우 HwpObject.버전으로 각 버전별 실행 이름을 설정합니다. 67 | if re.match("!HwpObject", name): 68 | # 120은 한글 2022의 경우입니다. 69 | # 현재 moniker를 통해 ROT에서 한글의 object를 가져옵니다. 70 | obje = running_coms.GetObject(moniker) 71 | # 가져온 object를 Dispatch를 통해 사용할수 있는 객체로 변환시킵니다. 72 | hwp_objects.append(obje.QueryInterface(pythoncom.IID_IDispatch)) 73 | return hwp_objects 74 | 75 | # %% ../nbs/02_api/02_functions.ipynb 7 76 | def get_absolute_path(path): 77 | """파일 절대 경로를 반환합니다.""" 78 | name = Path(path) 79 | return name.absolute().as_posix() 80 | 81 | # %% ../nbs/02_api/02_functions.ipynb 8 82 | def get_dll_path(package_name, dll_filename): 83 | """패키지에서 dll 경로를 확보합니다.""" 84 | try: 85 | with importlib.resources.path(package_name, dll_filename) as dll_path: 86 | return str(dll_path) 87 | except FileNotFoundError as e: 88 | raise FileNotFoundError( 89 | f"The DLL file '{dll_filename}' was not found in the package '{package_name}'." 90 | ) from e 91 | 92 | # %% ../nbs/02_api/02_functions.ipynb 9 93 | def add_dll_to_registry(dll_path, key_path): 94 | """레지스트리에 dll을 등록합니다.""" 95 | try: 96 | # Connect to the registry and open the specified key 97 | registry_key = winreg.OpenKey( 98 | winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE 99 | ) 100 | 101 | # Set the value for the new registry entry as a string (REG_SZ) 102 | winreg.SetValueEx( 103 | registry_key, "FilePathCheckerModule", 0, winreg.REG_SZ, dll_path 104 | ) 105 | 106 | # Close the registry key 107 | winreg.CloseKey(registry_key) 108 | print("DLL path added to registry as a string value successfully.") 109 | except WindowsError as e: 110 | print("Error while adding DLL path to registry: ", e) 111 | 112 | # %% ../nbs/02_api/02_functions.ipynb 10 113 | def get_registry_value(key_path, value_name): 114 | """레지스트리에 값이 있는지 확인해 봅니다.""" 115 | try: 116 | with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key: 117 | value, value_type = winreg.QueryValueEx(key, value_name) 118 | return value 119 | except FileNotFoundError: 120 | return None 121 | 122 | # %% ../nbs/02_api/02_functions.ipynb 11 123 | def check_dll(dll_path=None): 124 | """dll 모듈을 등록합니다.""" 125 | dll_path = dll_path if dll_path else get_dll_path("hwpapi", "FilePathCheckerModuleExample.dll") 126 | key_path = "SOFTWARE\\HNC\\HwpAutomation\\Modules" 127 | value_name = "FilePathCheckerModule" 128 | 129 | value = get_registry_value(key_path, value_name) 130 | 131 | if value is None: 132 | add_dll_to_registry(dll_path, key_path) 133 | return True 134 | 135 | # %% ../nbs/02_api/02_functions.ipynb 12 136 | def get_value(dict_, key): 137 | """딕셔너리에서 키를 찾아 값을 반환합니다. 반환할 값이 없으면 키에러와 함께 가능한 키를 알려줍니다.""" 138 | if key is None: 139 | return None 140 | try: 141 | return dict_[key] 142 | except KeyError: 143 | raise KeyError( 144 | f"{key}를 해당하는 키 중 찾을 수 없습니다. 키는 {', '.join(dict_.keys())} 중에 있어야 합니다." 145 | ) 146 | 147 | # %% ../nbs/02_api/02_functions.ipynb 13 148 | def get_key(dict_, value): 149 | """딕셔너리에서 값를 찾아 키를 반환합니다. 반환할 값이 없으면 키에러와 함께 가능한 키를 알려줍니다.""" 150 | if value is None: 151 | return None 152 | try: 153 | return dict([(v, k) for k, v in dict_.items()])[value] 154 | except KeyError: 155 | raise KeyError( 156 | f"{value}를 해당하는 키 중 찾을 수 없습니다. 키는 {', '.join(dict_.values())} 중에 있어야 합니다." 157 | ) 158 | 159 | # %% ../nbs/02_api/02_functions.ipynb 14 160 | def convert2int(_dict, value): 161 | if value is None: 162 | return value 163 | if isinstance(value, str): 164 | return get_value(_dict, value) 165 | if isinstance(value, int): 166 | return value 167 | if isinstance(value, float): 168 | return int(value) 169 | 170 | # %% ../nbs/02_api/02_functions.ipynb 15 171 | def set_pset(p, value_dict:dict): 172 | for field in dir(p): 173 | value = value_dict.get(field, None) 174 | if value is None: 175 | continue 176 | setattr(p, field, value) 177 | 178 | return p 179 | 180 | # %% ../nbs/02_api/02_functions.ipynb 16 181 | def get_charshape_pset(p): 182 | return {field: getattr(p, field) for field in char_fields} 183 | 184 | # %% ../nbs/02_api/02_functions.ipynb 17 185 | def set_charshape_pset( 186 | charshape_pset, value_dict:dict 187 | ): 188 | """ 189 | CharShape값을 입력하기 위한 함수입니다. 190 | char_fields에 정의된 키 값을 기반으로 파라미터를 세팅합니다. 191 | """ 192 | 193 | for field in char_fields: 194 | value = value_dict.get(field, None) 195 | if not value: 196 | continue 197 | setattr(charshape_pset, field, value) 198 | 199 | return charshape_pset 200 | 201 | # %% ../nbs/02_api/02_functions.ipynb 18 202 | def get_parashape_pset(p): 203 | 204 | return {field: getattr(p, field) for field in para_fields} 205 | 206 | # %% ../nbs/02_api/02_functions.ipynb 19 207 | def set_parashape_pset( 208 | parashape_pset, value_dict:dict, 209 | ): 210 | 211 | for field in para_fields: 212 | value = value_dict.get(field, None) 213 | if not value: 214 | continue 215 | setattr(parashape_pset, field, value) 216 | 217 | 218 | return parashape_pset 219 | 220 | # %% ../nbs/02_api/02_functions.ipynb 20 221 | def hex_to_rgb(hex_string): 222 | # Remove the "#" symbol if it exists 223 | if hex_string.startswith("#"): 224 | hex_string = hex_string[1:] 225 | 226 | # Convert the hex string to decimal integers 227 | red = int(hex_string[0:2], 16) 228 | green = int(hex_string[2:4], 16) 229 | blue = int(hex_string[4:], 16) 230 | 231 | # Return the RGB tuple 232 | return (red, green, blue) 233 | 234 | 235 | def get_rgb_tuple(color): 236 | # check if the input is already a tuple 237 | if isinstance(color, tuple): 238 | # validate each color 239 | if len(color) > 3: 240 | raise ValueError( 241 | f"colors should contains three compoents which represents RGB" 242 | ) 243 | 244 | for component in color: 245 | if component > 255: 246 | raise ValueError( 247 | f"number should be smaller than 255. the value is {color}" 248 | ) 249 | return color 250 | 251 | # if the input is a string, split it into a list of colors 252 | elif isinstance(color, str): 253 | colors = { 254 | "red": (255, 0, 0), 255 | "green": (0, 255, 0), 256 | "blue": (0, 0, 255), 257 | "black": (0, 0, 0), 258 | "white": (255, 255, 255), 259 | } 260 | 261 | if color in colors.keys(): 262 | return colors.get(color) 263 | 264 | # validate each color 265 | if not ( 266 | color.startswith("#") 267 | and len(color) == 7 268 | and all(c in "0123456789abcdefABCDEF" for c in color[1:]) 269 | ): 270 | raise ValueError(f"'{color}' is not a valid hexadecimal color.") 271 | 272 | # convert the list to a tuple and return it 273 | return hex_to_rgb(color) 274 | 275 | else: 276 | raise TypeError("Input must be a string or a tuple of colors.") 277 | 278 | # %% ../nbs/02_api/02_functions.ipynb 21 279 | def convert_to_hwp_color(color): 280 | 281 | if isinstance(color, int): 282 | return color 283 | 284 | if isinstance(color, str): # if the color is a string, we assume it's a hex string 285 | #hwp use bgr order 286 | colors = { 287 | "red": "0000FF", 288 | "green": "00FF00", 289 | "blue": "FF0000", 290 | "black": "000000", 291 | "white": "FFFFFF", 292 | } 293 | 294 | if color in colors.keys(): 295 | return int(colors.get(color), 16) 296 | 297 | # handle hex 298 | m = re.search("^#?([0-9A-Fa-f]{6})$", color) 299 | if m: 300 | color = m.group(1) 301 | return int(f"{color[4:6]}{color[2:4]}{color[0:2]}", 16) 302 | 303 | elif type(color) == tuple and len(color) == 3: # if the color is a tuple, we assume it's an (R,G,B) tuple 304 | return color[2]*65536 + color[1]*256 + color[0] 305 | else: 306 | raise ValueError(f"Unsupported color format: {color}") 307 | 308 | # %% ../nbs/02_api/02_functions.ipynb 22 309 | def convert_hwp_color_to_hex(color:int): 310 | if not color: 311 | return color 312 | text = f"{color:06x}" 313 | return f"#{text[4:6]}{text[2:4]}{text[:2]}" 314 | 315 | 316 | # %% ../nbs/02_api/02_functions.ipynb 24 317 | def mili2unit(value): 318 | """ 319 | 1 밀리는 283 hwpunit 입니다. 320 | """ 321 | return int(round(value*283, 0)) if value else value 322 | 323 | # %% ../nbs/02_api/02_functions.ipynb 25 324 | def unit2mili(value): 325 | return value/283 if value else value 326 | 327 | # %% ../nbs/02_api/02_functions.ipynb 26 328 | def point2unit(value): 329 | """ 330 | 1point는 100 hwpunit입니다. 331 | """ 332 | return int(round(value*100, 0)) if value else value 333 | 334 | # %% ../nbs/02_api/02_functions.ipynb 27 335 | def unit2point(value): 336 | return value / 100 if value else value 337 | 338 | # %% ../nbs/02_api/02_functions.ipynb 28 339 | def block_input(func): 340 | """ 341 | 함수가 실행될 동안 다른 입력을 할 수 없게 하는 기능을 가진 데코레이터입니다. 342 | """ 343 | def wrapper(app, *args, **kwargs): 344 | app.api.EditMode = 0 345 | result = func(app, *args, **kwargs) 346 | app.api.EditMode = 1 347 | return result 348 | return wrapper 349 | 350 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jun Damin 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. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-exclude * __pycache__ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HwpApi 2 | 3 | 4 | 5 | 여기서 [Tutorials](https://jundamin.github.io/hwpapi/)을 볼 수 있습니다. 6 | 7 | ## Install 8 | 9 | 이 패키지는 win32com을 통해 좀더 쉽게 한글 자동화를 하기 위한 패키지 10 | 입니다. 따라서, 한글 오피스가 설치된 Windows에서만 작동합니다. 리눅스나 11 | 한컴 오피스가 설치된 Mac OS에서는 작동하지 않습니다. 12 | 13 | 다른 일반적인 패키지와 같이 아래 명령어를 입력하면 설치할 수 있습니다. 14 | 15 | ``` sh 16 | pip install hwpapi 17 | ``` 18 | 19 | ## How to use 20 | 21 | 기본적으로는 wi32com을 통한 한컴 오피스 자동화를 보다 쉽게 사용하기 위해 22 | 개발한 패키지 입니다. 23 | 24 | 기존의 연동성을 최대한 유지하면서도 파이써닉하게 코드를 짤 수 있도록 25 | 개선하고자 하였습니다. 26 | 27 | [nbdev](https://nbdev.fast.ai/)에서 권장하는 스타일로 작성되다보니 28 | jupyter notebook이나 jupyter lab에서는 자동완성이 잘 작동되지만, VS 29 | Code에서는 자동완성이 작동 안할 수 있습니다. 30 | 31 | ### 기존 코드와 연동성 비교하기 32 | 33 | [회사원 코딩](https://employeecoding.tistory.com/72)에 가보시면 아래와 34 | 같이 자동화 코드가 있습니다. 35 | 36 | ``` python 37 | import win32com.client as win32 38 | hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject") 39 | hwp.XHwpWindows.Item(0).Visible = True 40 | 41 | act = hwp.CreateAction("InsertText") 42 | pset = act.CreateSet() 43 | pset.SetItem("Text", "Hello\r\nWorld!") 44 | act.Execute(pset) 45 | ``` 46 | 47 | 이 코드는 기본적으로 장황하다고 볼 만한 상황입니다. 이 코드를 `HwpApi`를 48 | 사용하면 아래와 같이 간결하게 정리가 됨을 볼 수 있습니다. 49 | 50 | ``` python 51 | from hwpapi.core import App 52 | 53 | app = App() 54 | action = app.actions.InsertText() 55 | p = action.pset 56 | p.Text = "Hello\r\nWorld!" 57 | action.run() 58 | ``` 59 | 60 | True 61 | 62 | 이렇게 자주 사용하는 기능은 함수로 만들었습니다. 63 | 64 | ``` python 65 | app.insert_text("Hello\r\nWorld!") 66 | ``` 67 | 68 | 글자 모양을 바꾸는 것은 자주 있는 함수 입니다. win32com을 사용하면 69 | 아래와 같이 작성해야 합니다. 70 | 71 | ``` python 72 | Act = hwp.CreateAction("CharShape") 73 | Set = Act.CreateSet() 74 | Act.GetDefault(Set) 75 | Set.Item("Italic") 76 | Set.SetItem("Italic", 1) 77 | Act.Execute(Set) 78 | ``` 79 | 80 | 이렇게 자주 사용되는 기능은 함수로 만들어 사용할 수 있게 했습니다. 81 | 82 | ``` python 83 | app.set_charshape(italic=True) 84 | ``` 85 | 86 | True 87 | 88 | 코드를 보시면 hwp를 세팅하는 부분이 간략해졌습니다. 또한 파라미터 설정이 89 | 파이썬 객체처럼 설정할 수 있게 변경 되어 있는 것을 볼 수 있습니다. 90 | 91 | 이런 식으로 파이썬에서 사용하기 쉽게 만들었습니다. 92 | 93 | ## 왜 HwpApi를 만들었나요? 94 | 95 | 가장 큰 이유는 스스로 사용하기 위해서 입니다. 직장인으로 많은 한글 96 | 문서를 편집하고 작성하곤 하는데 단순 반복업무가 너무 많다는 것이 97 | 불만이었습니다. 이런 문제를 해결하는 방법으로 한글 자동화에 대한 98 | 이야기를 파이콘에서 보게 되었습니다. 특히 ‘회사원 코딩’ 님의 블로그와 99 | 영상이 많은 참조가 되었습니다. 100 | 101 | 다만 그 과정에서 설명자료가 부족하기도 하고 예전에 작성했던 코드들을 102 | 자꾸 찾아보게 되면서 아래아 한글 용 파이썬 패키지가 있으면 좋겠다는 103 | 생각을 했습니다. 특히 업무를 하면서 엑셀 자동화를 위해 xlwings를 사용해 104 | 보면서 파이썬으로 사용하기 쉽게 만든 라이브러리가 코딩 작업 효율을 엄청 105 | 올린다는 것을 깨닫게 되었습니다. 106 | 107 | 제출 마감까지 해야 할 일들을 빠르게 하기 위해서 빠르게 한글 자동화가 108 | 된다면 좋겠다는 생각으로 만들게 되었습니다. 109 | 110 | 기본적인 철학은 xlwings을 따라하고 있습니다. 기본적으로는 자주 쓰이는 111 | 항목들을 사용하기 쉽게 정리한 메소드 등으로 구현하고, 부족한 부분은 112 | [`App.api`](https://JunDamin.github.io/hwpapi/02_api/core.html#app.api)형태로 113 | `win32com`으로 하는 것과 동일한 작업이 가능하게 하여 한글 api의 모든 114 | 기능을 사용할 수 있도록 구현하였습니다. 115 | 116 | 메소드로 만드는 것에는 아직 고민이 있습니다. chain과 같은 형태로 117 | 여러가지 콤비네이션을 사전에 세팅을 해야 하나 싶은 부분도 있고 실제로 118 | 유용하게 사용할 수 있는 여러가지 아이템 등도 있어서 어떤 부분까지 이 119 | 패키지에 구현할지는 고민하고 있습니다. 120 | 121 | 다만 이런 형태의 작업을 통해서 어쩌면 hwp api wrapper가 활성화 되어서 122 | 단순 작업을 자동화 할 수 있기를 바라고 있습니다. 123 | -------------------------------------------------------------------------------- /_proc/01_tutorials/01_app_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1b19722e-b225-4464-919f-7d69e8aecb3d", 6 | "metadata": {}, 7 | "source": [ 8 | "---\n", 9 | "description: 사용 기초\n", 10 | "output-file: tutorial.html\n", 11 | "title: 튜토리얼\n", 12 | "---\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 72, 25 | "id": "5dfe2ff7-0f7b-4fae-8a1c-9a09a85cd389", 26 | "metadata": { 27 | "language": "python" 28 | }, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "The autoreload extension is already loaded. To reload it, use:\n", 35 | " %reload_ext autoreload\n" 36 | ] 37 | } 38 | ], 39 | "source": [] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "85e1facc-2f64-4da1-8351-ba21d5298dda", 44 | "metadata": {}, 45 | "source": [ 46 | "한컴 오피스는 액션이라는 것을 통해서 다양한 문서 편집을 가능하게 하고 있습니다. 그런 편집을 파이썬에서 보다 쉽게 사용하기 위해 만든 라이브러리입니다.\n", 47 | "\n", 48 | "기본적인 컨셉은 win32com을 쉽게 사용할 수 있게 개편한 것입니다.\n", 49 | "\n", 50 | "문서 편집의 기본 기능인 \n", 51 | "1. 문장 입력하기\n", 52 | "2. 커서 이동하기\n", 53 | "2. 영역 선택하기\n", 54 | "2. 서식 변경하기\n", 55 | "3. 텍스트 가져오기 " 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 73, 61 | "id": "cccb95a9-5588-40fa-ade4-bb0357b5e2ea", 62 | "metadata": { 63 | "language": "python" 64 | }, 65 | "outputs": [], 66 | "source": [ 67 | "from hwpapi.core import App" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "id": "fe377bfb-a6cf-41fc-bc37-35f76dfe2600", 73 | "metadata": {}, 74 | "source": [ 75 | "[`App`](https://JunDamin.github.io/hwpapi/02_api/core.html#app)은 기존 hwpapi를 wrapping 하여 보다 사용하기 쉽게 만드는 것을 목적으로 하고 있는 클래스 입니다.\n", 76 | "\n", 77 | "`hwpapi`는 한글 컨트롤을 조합해서 자주사용했던 기능을 구현하였습니다.\n", 78 | "또한, api 메소드를 통해 win32com의 방식으로 모두 사용할 수 있어 다양하게 사용할 수 있습니다.\n", 79 | "\n", 80 | "아래와 같이 App 객체를 생성하면 한글이 열립니다." 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 74, 86 | "id": "e8f1fd20-f6e4-48ec-8cba-e4c7fcbd7109", 87 | "metadata": { 88 | "language": "python" 89 | }, 90 | "outputs": [], 91 | "source": [ 92 | "app = App()" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "0f9a7312-f991-4e4e-8e44-c0ebcd263863", 98 | "metadata": {}, 99 | "source": [ 100 | "한글을 조작할 때는 크게 `action`과 `hwpctrl method`로 나눌 수 있습니다.\n", 101 | "`action`은 파라미터를 설정하고 그것을 실행시켜서 작동하는 방식으로 사용자 단축키를 누르면 실행되는 명령에 가깝다면\n", 102 | "`hwpctrl method`는 한글 자동화 모듈이 제공하는 것으로 사용자 입력에서는 신경쓰지 않아도 될 부분들을 처리하는 것이라고 보시면 됩니다.\n", 103 | "\n", 104 | "[공식 개발 매뉴얼](https://www.hancom.com/board/devmanualList.do)에 여러 `action`과 `parameter`, `method`의 설명을 제공하고 있습니다.\n", 105 | "아쉽게도 문서가 잘 관리되고 있지는 않으며, `hwpctrl`의 `method`들을 보면 명시되어 있지 않은 기능들이 개발되어 있음을 알 수 있습니다. \n", 106 | "\n", 107 | "앞으로 개발해 나가면서 확인된 액션과 메소드 들을 정리해 나가고자 합니다." 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "a1807076-daf6-4a69-9a44-b2544f6dfa53", 113 | "metadata": {}, 114 | "source": [ 115 | "`action`은 `app`에서 생성할 수 있습니다.\n", 116 | "방식은 크게 2가지로 직접 `action key`를 입력하는 방법과 `actions`에 있는 `action` 객체를 생성하는 방법이 있습니다.\n", 117 | "아래 두 방식은 동일한 `Action` 객체를 생성합니다." 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 75, 123 | "id": "3baa1486-30bd-403e-b6c1-e273f18af05e", 124 | "metadata": { 125 | "language": "python" 126 | }, 127 | "outputs": [ 128 | { 129 | "data": { 130 | "text/plain": [ 131 | "" 132 | ] 133 | }, 134 | "execution_count": 75, 135 | "metadata": {}, 136 | "output_type": "execute_result" 137 | } 138 | ], 139 | "source": [ 140 | "action1 = app.create_action(\"InsertText\")\n", 141 | "action1" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 76, 147 | "id": "0163beab-1a00-4503-bb20-59230682fcec", 148 | "metadata": { 149 | "language": "python" 150 | }, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/plain": [ 155 | "" 156 | ] 157 | }, 158 | "execution_count": 76, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "action2 = app.actions.InsertText()\n", 165 | "action2" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "id": "1687251c-d10e-48ee-b26f-679b71b8f7b0", 171 | "metadata": {}, 172 | "source": [ 173 | "`Delete`(지우기), `BreakPara`(줄바꿈) 등 많은 `action`은 파라미터 세팅이 필요 없습니다.\n", 174 | "하지만 위에서 생성한 `InsertText`객체는 입력할 텍스트 값을 넣어주어야 합니다.\n", 175 | "이는 `parameter`를 통해 설정할 수 있습니다." 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 77, 181 | "id": "803629f4-a4c1-4a8b-b7e0-622619749568", 182 | "metadata": { 183 | "language": "python" 184 | }, 185 | "outputs": [], 186 | "source": [ 187 | "action1.pset.Text = \"입력하기\"" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "id": "d26d8406-c324-43c3-a0e6-cc5e3c1f019d", 193 | "metadata": {}, 194 | "source": [ 195 | "파라미터를 설정한 후 다음과 같이 액션을 실행하면 텍스트가 입력되는 걸 볼 수 있습니다." 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 78, 201 | "id": "3b7aa7f1-009f-4b23-a61a-dd153db363e0", 202 | "metadata": { 203 | "language": "python" 204 | }, 205 | "outputs": [ 206 | { 207 | "data": { 208 | "text/plain": [ 209 | "True" 210 | ] 211 | }, 212 | "execution_count": 78, 213 | "metadata": {}, 214 | "output_type": "execute_result" 215 | } 216 | ], 217 | "source": [ 218 | "action1.run()" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 79, 224 | "id": "4d244dc4-01c9-4faf-9381-7f35fbe467c7", 225 | "metadata": { 226 | "language": "python" 227 | }, 228 | "outputs": [], 229 | "source": [ 230 | "charshape = app.actions.CharShape()" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 80, 236 | "id": "c0f619b6-2fe0-4217-b1e5-5c1e029f4726", 237 | "metadata": { 238 | "language": "python" 239 | }, 240 | "outputs": [], 241 | "source": [ 242 | "charshape.pset.Height = app.api.PointToHwpUnit(20)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 81, 248 | "id": "78d0cd48-2a41-480e-b284-21db07b86343", 249 | "metadata": { 250 | "language": "python" 251 | }, 252 | "outputs": [ 253 | { 254 | "data": { 255 | "text/plain": [ 256 | "True" 257 | ] 258 | }, 259 | "execution_count": 81, 260 | "metadata": {}, 261 | "output_type": "execute_result" 262 | } 263 | ], 264 | "source": [ 265 | "charshape.run()" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 82, 271 | "id": "7f637594-0af7-4065-9d8e-0b84e67bd356", 272 | "metadata": { 273 | "language": "python" 274 | }, 275 | "outputs": [ 276 | { 277 | "data": { 278 | "text/plain": [ 279 | "True" 280 | ] 281 | }, 282 | "execution_count": 82, 283 | "metadata": {}, 284 | "output_type": "execute_result" 285 | } 286 | ], 287 | "source": [ 288 | "action2.pset.Text = \"크게 입력하기\"\n", 289 | "action2.run()" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "id": "264128ea-247e-4437-9a01-02fc5c682c54", 295 | "metadata": {}, 296 | "source": [ 297 | "## 입력하기" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "id": "3cefd551-6eb9-4c3c-bdac-64167128b7db", 303 | "metadata": {}, 304 | "source": [ 305 | "입력은 가장 자주 사용하는 것이기 때문에 다음과 같이 [`App`](https://JunDamin.github.io/hwpapi/02_api/core.html#app)의 메소드로 만들었습니다." 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 83, 311 | "id": "d770711b-1f4c-420d-90f9-c078469975df", 312 | "metadata": { 313 | "language": "python" 314 | }, 315 | "outputs": [], 316 | "source": [ 317 | "app.insert_text(\"더 크게 입력하기\", height=30)" 318 | ] 319 | }, 320 | { 321 | "cell_type": "code", 322 | "execution_count": 84, 323 | "id": "7151e810-756c-484c-a3a6-c08bd79e034c", 324 | "metadata": { 325 | "language": "python" 326 | }, 327 | "outputs": [], 328 | "source": [ 329 | "action = app.actions.Select()" 330 | ] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "id": "9c4cd9de-760a-451f-8d6f-b4f002b13ae4", 335 | "metadata": {}, 336 | "source": [ 337 | "## 이동하기\n", 338 | "\n", 339 | "크게 두가지 방법이 있습니다. \n", 340 | "\n", 341 | "액션을 사용하는 방법과 단어를 찾아가는 방법이 있습니다." 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 85, 347 | "id": "790d7e99-6cf2-491c-865e-c4f281528447", 348 | "metadata": { 349 | "language": "python" 350 | }, 351 | "outputs": [ 352 | { 353 | "data": { 354 | "text/plain": [ 355 | "True" 356 | ] 357 | }, 358 | "execution_count": 85, 359 | "metadata": {}, 360 | "output_type": "execute_result" 361 | } 362 | ], 363 | "source": [ 364 | "app.actions.MoveColumnBegin().run()" 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "id": "acd1ca4a-e54f-4c63-b514-28f9779c36f1", 370 | "metadata": {}, 371 | "source": [ 372 | "actions를 아래와 같이 하여 찾는 경로를 깗게 할 수도 있습니다." 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 86, 378 | "id": "b55198fe-c853-45b5-98b3-327c9400b600", 379 | "metadata": { 380 | "language": "python" 381 | }, 382 | "outputs": [ 383 | { 384 | "data": { 385 | "text/plain": [ 386 | "True" 387 | ] 388 | }, 389 | "execution_count": 86, 390 | "metadata": {}, 391 | "output_type": "execute_result" 392 | } 393 | ], 394 | "source": [ 395 | "actions = app.actions\n", 396 | "actions.MoveColumnBegin().run()" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": 87, 402 | "id": "1ed03ea8-be34-42ab-9ce3-deed5fc4526f", 403 | "metadata": { 404 | "language": "python" 405 | }, 406 | "outputs": [ 407 | { 408 | "data": { 409 | "text/plain": [ 410 | "False" 411 | ] 412 | }, 413 | "execution_count": 87, 414 | "metadata": {}, 415 | "output_type": "execute_result" 416 | } 417 | ], 418 | "source": [ 419 | "app.find_text(\"해해\")" 420 | ] 421 | }, 422 | { 423 | "cell_type": "markdown", 424 | "id": "97a580d6-cf70-4803-87af-44f12fab2e3e", 425 | "metadata": {}, 426 | "source": [ 427 | "## 텍스트 선택하기\n", 428 | "\n", 429 | "문서에서 드래그한 것과 같이 영역을 선택할 수 있습니다." 430 | ] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "execution_count": 88, 435 | "id": "5395dfcd-7219-443f-9469-b4a08fa5092b", 436 | "metadata": { 437 | "language": "python" 438 | }, 439 | "outputs": [ 440 | { 441 | "data": { 442 | "text/plain": [ 443 | "(True, True)" 444 | ] 445 | }, 446 | "execution_count": 88, 447 | "metadata": {}, 448 | "output_type": "execute_result" 449 | } 450 | ], 451 | "source": [ 452 | "app.select_text(\"Para\")" 453 | ] 454 | }, 455 | { 456 | "cell_type": "markdown", 457 | "id": "d719942e-7b79-4678-8549-5a3798ac54af", 458 | "metadata": {}, 459 | "source": [ 460 | "## 서식 넣기\n", 461 | "\n", 462 | "선택한 영역에 글자 서식을 넣을 수 있습니다." 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": 89, 468 | "id": "7f0e8f66-4b74-4f75-bff8-3200a2f08ec7", 469 | "metadata": { 470 | "language": "python" 471 | }, 472 | "outputs": [ 473 | { 474 | "data": { 475 | "text/plain": [ 476 | "True" 477 | ] 478 | }, 479 | "execution_count": 89, 480 | "metadata": {}, 481 | "output_type": "execute_result" 482 | } 483 | ], 484 | "source": [ 485 | "app.set_charshape(fontname=\"바탕체\", height=25, bold=True)" 486 | ] 487 | }, 488 | { 489 | "cell_type": "markdown", 490 | "id": "6fd0ae3d", 491 | "metadata": {}, 492 | "source": [ 493 | "문단 서식 또한 가능합니다." 494 | ] 495 | }, 496 | { 497 | "cell_type": "code", 498 | "execution_count": 90, 499 | "id": "b40483d3", 500 | "metadata": { 501 | "language": "python" 502 | }, 503 | "outputs": [ 504 | { 505 | "data": { 506 | "text/plain": [ 507 | "True" 508 | ] 509 | }, 510 | "execution_count": 90, 511 | "metadata": {}, 512 | "output_type": "execute_result" 513 | } 514 | ], 515 | "source": [ 516 | "app.set_parashape(left_margin=50)" 517 | ] 518 | }, 519 | { 520 | "cell_type": "markdown", 521 | "id": "707457d9-c86b-43b4-bb0d-fe9456e0486f", 522 | "metadata": {}, 523 | "source": [ 524 | "## 텍스트 가져오기" 525 | ] 526 | }, 527 | { 528 | "cell_type": "markdown", 529 | "id": "3b63bb75", 530 | "metadata": {}, 531 | "source": [ 532 | "현재 위치의 문장이나 텍스트를 가져 올 수 있습니다.\n", 533 | "기본은 현재 문장의 시작에서 문장의 끝을 선택합니다." 534 | ] 535 | }, 536 | { 537 | "cell_type": "code", 538 | "execution_count": 91, 539 | "id": "c0ea4e59-f4be-4bd3-8451-e97491f7f6fa", 540 | "metadata": { 541 | "language": "python" 542 | }, 543 | "outputs": [ 544 | { 545 | "data": { 546 | "text/plain": [ 547 | "'입력하기크게 입력하기더 크게 입력하기\\r\\n'" 548 | ] 549 | }, 550 | "execution_count": 91, 551 | "metadata": {}, 552 | "output_type": "execute_result" 553 | } 554 | ], 555 | "source": [ 556 | "app.get_text()" 557 | ] 558 | }, 559 | { 560 | "cell_type": "markdown", 561 | "id": "23add90d", 562 | "metadata": {}, 563 | "source": [ 564 | "선택 영역만 가져올 수도 있습니다." 565 | ] 566 | }, 567 | { 568 | "cell_type": "code", 569 | "execution_count": 92, 570 | "id": "085dc66d-760c-4e22-90d7-79f974e4aa61", 571 | "metadata": { 572 | "language": "python" 573 | }, 574 | "outputs": [ 575 | { 576 | "data": { 577 | "text/plain": [ 578 | "'입력하기크게 입력하기더 크게 입력하기\\n'" 579 | ] 580 | }, 581 | "execution_count": 92, 582 | "metadata": {}, 583 | "output_type": "execute_result" 584 | } 585 | ], 586 | "source": [ 587 | "app.get_selected_text()" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": null, 593 | "id": "5511b9fe-cf31-484d-ad8b-f6176586f95a", 594 | "metadata": { 595 | "language": "python" 596 | }, 597 | "outputs": [], 598 | "source": [] 599 | } 600 | ], 601 | "metadata": { 602 | "kernelspec": { 603 | "display_name": "python3", 604 | "language": "python", 605 | "name": "python3" 606 | }, 607 | "language_info": { 608 | "codemirror_mode": { 609 | "name": "ipython", 610 | "version": 3 611 | }, 612 | "file_extension": ".py", 613 | "mimetype": "text/x-python", 614 | "name": "python", 615 | "nbconvert_exporter": "python", 616 | "pygments_lexer": "ipython3", 617 | "version": "3.10.1" 618 | }, 619 | "widgets": { 620 | "application/vnd.jupyter.widget-state+json": { 621 | "state": {}, 622 | "version_major": 2, 623 | "version_minor": 0 624 | } 625 | } 626 | }, 627 | "nbformat": 4, 628 | "nbformat_minor": 5 629 | } 630 | -------------------------------------------------------------------------------- /_proc/01_tutorials/02_find_and_replace.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "---\n", 8 | "title: \"찾아바꾸기 기능 활용\"\n", 9 | "description: 찾아바꾸기를 활용한 파이썬 자동화 사례\n", 10 | "output-file: find_replace.html\n", 11 | "---" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "## 문제설정\n", 26 | "\n", 27 | "문서 작업을 하면서 같은 의미지만 다르게 작성하여 형식을 통일하기 위해 문서를 처음부터 검토해야 하는 경우가 있습니다.\n", 28 | "예를 들어 \"2022년\"이라고 쓰는 경우도 있고 \"'22년\"으로 적는 경우도 있습니다. 이를 모두 2022년으로 작성 방식을 통일하고자 한다면 찾아바꾸기를 통해 쉽게 달성할 수 있습니다.\n", 29 | "\n", 30 | "만약 이런 바꿔야 하는 단어가 수십개가 된다면 어떻게 될까요?\n", 31 | "붙여써야 하는 경우, 자주 틀리는 오탈자, 영문명으로 바로 작성하거나 이니셜로만 작성하는 등, 수십개의 케이스를 모두 적용하는 것은 상당히 귀찮고 오류가 발생하기 쉬운 일입니다.\n", 32 | "\n", 33 | "이런 문제를 `hwpapi`를 사용해 해결해 보고자 합니다.\n", 34 | "\n", 35 | "[국토부 보도자료](http://www.molit.go.kr/USR/NEWS/m_71/dtl.jsp?id=95086857)를 보면 임대차 시장 안정 및 3분기 부동산 정상화 방안이라는 문서를 볼 수 있습니다.\n", 36 | "\n", 37 | "여기서 보면 '주거 안정'이라고 띄어 쓴 경우와 '주거안정'이라고 붙여쓴 경우가 있습니다.\n", 38 | "![](img/주거안정.png)\n", 39 | "\n", 40 | "유사하게 '분양가 상한제'와 같이 띄어 쓴 경우와 '분양가상한제'라고 붙여 쓴 경우가 있죠.\n", 41 | "![](img/분양가상한제1.png)\n", 42 | "![](img/분양가상한제2.png)\n", 43 | "\n", 44 | "또한 '시범사업지'와 '시범 사업지'와 같이 경우에 따라 붙이거나 띄는 경우는 한국어 특성상 자주 발생합니다. \n", 45 | "![](img/시범사업지.png)\n", 46 | "\n", 47 | "이런 항목을 모두 붙여 쓰는 스크립트를 짜보도록 하겠습니다.\n", 48 | "\n", 49 | "해야 할 일은 \n", 50 | "\n", 51 | "1. 문서 불러오기\n", 52 | "2. 기존과 변경할 것 목록 만들기\n", 53 | "3. 찾아 바꾸기\n", 54 | "\n", 55 | "이렇게 3단계로 구성됩니다." 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "### 문서 불러오기\n", 63 | "\n", 64 | "우선 패키지를 불러오고 문서를 불러 옵니다.\n", 65 | "저는 `hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp` 파일을 읽어 오겠습니다." 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 1, 71 | "metadata": { 72 | "language": "python" 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "from hwpapi.core import App\n", 77 | "\n", 78 | "app = App()\n", 79 | "app.open(\"hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp\")" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "### 기존 단어와 변경할 단어 목록 만들기\n", 87 | "\n", 88 | "아래와 같이 기존 단어와 변경할 단어를 만들어 둡니다.\n", 89 | "여기서는 단순히 `list`를 사용했지만, `pandas` 등을 사용하면 엑셀 파일에서 관리할 수 있습니다." 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 2, 95 | "metadata": { 96 | "language": "python" 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "words = [(\"분양가 상한제\", \"분양가상한제\"), (\"주거안정\", \"주거 안정\"), (\"시범사업지\", \"시범 사업지\")]" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "### 찾아바꾸기\n", 108 | "\n", 109 | "이렇게 까지 되면 나머지는 간단합니다. `words`를 순환 하면서 반복해 주기만 하면 됩니다.\n", 110 | "모두 찾아바꾸기를 하면 어디를 바꾸었는지 확인하기 어렵기 때문에 바꾼 단어는 붉은 색으로 처리해서 쉽게 눈으로 확인해 볼 수 있게 하겠습니다.\n", 111 | "그러기 위해서 [`CharShape`](https://JunDamin.github.io/hwpapi/02_api/dataclasses.html#charshape)이라고 하는 `dataclass`를 불러오겠습니다." 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 3, 117 | "metadata": { 118 | "language": "python" 119 | }, 120 | "outputs": [], 121 | "source": [ 122 | "from hwpapi.dataclasses import CharShape\n", 123 | "\n", 124 | "for old, new in words:\n", 125 | " app.replace_all(old, new, new_charshape=CharShape(text_color=\"#FF0000\"))" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "코드를 실행하고 나면 아래와 같이 바뀐 단어는 붉은색으로 표시되게 됩니다.\n", 133 | "![](img/주거안정_변경.png)\n", 134 | "![](img/시범사업지_변경.png)\n", 135 | "\n", 136 | "이렇게 변경된 사항을 눈으로 확인하고 최종적으로 단축키 등으로 정리하면 문서 전체적으로 맞춰야 하는 단어나 자주 틀리는 오탈자를 쉽게 관리 할 수 있게 됩니다." 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 10, 142 | "metadata": { 143 | "language": "python" 144 | }, 145 | "outputs": [], 146 | "source": [] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 31, 151 | "metadata": { 152 | "language": "python" 153 | }, 154 | "outputs": [ 155 | { 156 | "ename": "KeyError", 157 | "evalue": "'DownOCell를 해당하는 키 중 찾을 수 없습니다. 키는 Main, CurList, TopOfFile, BottomOfFile, TopOfList, BottomOfList, StartOfPara, EndOfPara, StartOfWord, EndOfWord, NextPara, PrevPara, NextPos, PrevPos, NextPosEx, PrevPosEx, NextChar, PrevChar, NextWord, PrevWord, NextLine, PrevLine, StartOfLine, EndOfLine, ParentList, TopLevelList, RootList, CurrentCaret, LeftOfCell, RightOfCell, UpOfCell, DownOfCell, StartOfCell, EndOfCell, TopOfCell, BottomOfCell, ScrPos, ScanPos 중에 있어야 합니다.'", 158 | "output_type": "error", 159 | "traceback": [ 160 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 161 | "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", 162 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\functions.py:114\u001b[0m, in \u001b[0;36mget_value\u001b[1;34m(dict_, key)\u001b[0m\n\u001b[0;32m 113\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[1;32m--> 114\u001b[0m \u001b[39mreturn\u001b[39;00m dict_[key]\n\u001b[0;32m 115\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n", 163 | "\u001b[1;31mKeyError\u001b[0m: 'DownOCell'", 164 | "\nDuring handling of the above exception, another exception occurred:\n", 165 | "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", 166 | "Cell \u001b[1;32mIn[31], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m app\u001b[39m.\u001b[39;49mmove(\u001b[39m\"\u001b[39;49m\u001b[39mDownOCell\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n", 167 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\core.py:497\u001b[0m, in \u001b[0;36mmove\u001b[1;34m(app, key, para, pos)\u001b[0m\n\u001b[0;32m 493\u001b[0m \u001b[39m@patch\u001b[39m\n\u001b[0;32m 494\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mmove\u001b[39m(app: App, key\u001b[39m=\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mScanPos\u001b[39m\u001b[39m\"\u001b[39m, para\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m, pos\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m):\n\u001b[0;32m 495\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"키워드를 바탕으로 캐럿 위치를 이동시킵니다.\"\"\"\u001b[39;00m\n\u001b[1;32m--> 497\u001b[0m move_id \u001b[39m=\u001b[39m get_value(move_ids, key)\n\u001b[0;32m 498\u001b[0m \u001b[39mreturn\u001b[39;00m app\u001b[39m.\u001b[39mapi\u001b[39m.\u001b[39mMovePos(moveID\u001b[39m=\u001b[39mmove_id, Para\u001b[39m=\u001b[39mpara, pos\u001b[39m=\u001b[39mpos)\n", 168 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\functions.py:116\u001b[0m, in \u001b[0;36mget_value\u001b[1;34m(dict_, key)\u001b[0m\n\u001b[0;32m 114\u001b[0m \u001b[39mreturn\u001b[39;00m dict_[key]\n\u001b[0;32m 115\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n\u001b[1;32m--> 116\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mKeyError\u001b[39;00m(\n\u001b[0;32m 117\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m{\u001b[39;00mkey\u001b[39m}\u001b[39;00m\u001b[39m를 해당하는 키 중 찾을 수 없습니다. 키는 \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39m'\u001b[39m\u001b[39m, \u001b[39m\u001b[39m'\u001b[39m\u001b[39m.\u001b[39mjoin(dict_\u001b[39m.\u001b[39mkeys())\u001b[39m}\u001b[39;00m\u001b[39m 중에 있어야 합니다.\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 118\u001b[0m )\n", 169 | "\u001b[1;31mKeyError\u001b[0m: 'DownOCell를 해당하는 키 중 찾을 수 없습니다. 키는 Main, CurList, TopOfFile, BottomOfFile, TopOfList, BottomOfList, StartOfPara, EndOfPara, StartOfWord, EndOfWord, NextPara, PrevPara, NextPos, PrevPos, NextPosEx, PrevPosEx, NextChar, PrevChar, NextWord, PrevWord, NextLine, PrevLine, StartOfLine, EndOfLine, ParentList, TopLevelList, RootList, CurrentCaret, LeftOfCell, RightOfCell, UpOfCell, DownOfCell, StartOfCell, EndOfCell, TopOfCell, BottomOfCell, ScrPos, ScanPos 중에 있어야 합니다.'" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "app.move(\"DownOfCell\")" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 46, 180 | "metadata": { 181 | "language": "python" 182 | }, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/plain": [ 187 | "['Application',\n", 188 | " 'ArcType',\n", 189 | " 'AutoNumType',\n", 190 | " 'BorderShape',\n", 191 | " 'BreakWordLatin',\n", 192 | " 'BrushType',\n", 193 | " 'CLSID',\n", 194 | " 'Canonical',\n", 195 | " 'CellApply',\n", 196 | " 'CellShape',\n", 197 | " 'CharShadowType',\n", 198 | " 'CharShape',\n", 199 | " 'CheckXObject',\n", 200 | " 'Clear',\n", 201 | " 'ColDefType',\n", 202 | " 'ColLayoutType',\n", 203 | " 'ConvertPUAHangulToUnicode',\n", 204 | " 'CreateAction',\n", 205 | " 'CreateField',\n", 206 | " 'CreateID',\n", 207 | " 'CreateMode',\n", 208 | " 'CreatePageImage',\n", 209 | " 'CreateSet',\n", 210 | " 'CrookedSlash',\n", 211 | " 'CurFieldState',\n", 212 | " 'CurMetatagState',\n", 213 | " 'CurSelectedCtrl',\n", 214 | " 'DSMark',\n", 215 | " 'DbfCodeType',\n", 216 | " 'DeleteCtrl',\n", 217 | " 'Delimiter',\n", 218 | " 'DrawAspect',\n", 219 | " 'DrawFillImage',\n", 220 | " 'DrawShadowType',\n", 221 | " 'EditMode',\n", 222 | " 'Encrypt',\n", 223 | " 'EndSize',\n", 224 | " 'EndStyle',\n", 225 | " 'EngineProperties',\n", 226 | " 'ExportStyle',\n", 227 | " 'FieldExist',\n", 228 | " 'FileTranslate',\n", 229 | " 'FillAreaType',\n", 230 | " 'FindCtrl',\n", 231 | " 'FindDir',\n", 232 | " 'FindPrivateInfo',\n", 233 | " 'FontType',\n", 234 | " 'GetBinDataPath',\n", 235 | " 'GetCurFieldName',\n", 236 | " 'GetCurMetatagName',\n", 237 | " 'GetFieldList',\n", 238 | " 'GetFieldText',\n", 239 | " 'GetFileInfo',\n", 240 | " 'GetFontList',\n", 241 | " 'GetHeadingString',\n", 242 | " 'GetMessageBoxMode',\n", 243 | " 'GetMetatagList',\n", 244 | " 'GetMetatagNameText',\n", 245 | " 'GetMousePos',\n", 246 | " 'GetPageText',\n", 247 | " 'GetPos',\n", 248 | " 'GetPosBySet',\n", 249 | " 'GetScriptSource',\n", 250 | " 'GetSelectedPos',\n", 251 | " 'GetSelectedPosBySet',\n", 252 | " 'GetText',\n", 253 | " 'GetTextFile',\n", 254 | " 'GetTranslateLangList',\n", 255 | " 'GetUserInfo',\n", 256 | " 'Gradation',\n", 257 | " 'GridMethod',\n", 258 | " 'GridViewLine',\n", 259 | " 'GutterMethod',\n", 260 | " 'HAction',\n", 261 | " 'HAlign',\n", 262 | " 'HParameterSet',\n", 263 | " 'Handler',\n", 264 | " 'Hash',\n", 265 | " 'HatchStyle',\n", 266 | " 'HeadCtrl',\n", 267 | " 'HeadType',\n", 268 | " 'HeightRel',\n", 269 | " 'Hiding',\n", 270 | " 'HorzRel',\n", 271 | " 'HwpLineType',\n", 272 | " 'HwpLineWidth',\n", 273 | " 'HwpOutlineStyle',\n", 274 | " 'HwpOutlineType',\n", 275 | " 'HwpUnderlineShape',\n", 276 | " 'HwpUnderlineType',\n", 277 | " 'HwpZoomType',\n", 278 | " 'ImageFormat',\n", 279 | " 'ImportStyle',\n", 280 | " 'InitHParameterSet',\n", 281 | " 'InitScan',\n", 282 | " 'Insert',\n", 283 | " 'InsertBackgroundPicture',\n", 284 | " 'InsertCtrl',\n", 285 | " 'InsertPicture',\n", 286 | " 'IsActionEnable',\n", 287 | " 'IsCommandLock',\n", 288 | " 'IsEmpty',\n", 289 | " 'IsModified',\n", 290 | " 'IsPrivateInfoProtected',\n", 291 | " 'IsTrackChange',\n", 292 | " 'IsTrackChangePassword',\n", 293 | " 'KeyIndicator',\n", 294 | " 'LastCtrl',\n", 295 | " 'LineSpacingMethod',\n", 296 | " 'LineWrapType',\n", 297 | " 'LockCommand',\n", 298 | " 'LunarToSolar',\n", 299 | " 'LunarToSolarBySet',\n", 300 | " 'MacroState',\n", 301 | " 'MailType',\n", 302 | " 'MetatagExist',\n", 303 | " 'MiliToHwpUnit',\n", 304 | " 'ModifyFieldProperties',\n", 305 | " 'ModifyMetatagProperties',\n", 306 | " 'MovePos',\n", 307 | " 'MoveToField',\n", 308 | " 'MoveToMetatag',\n", 309 | " 'NumberFormat',\n", 310 | " 'Numbering',\n", 311 | " 'Open',\n", 312 | " 'PageCount',\n", 313 | " 'PageNumPosition',\n", 314 | " 'PageType',\n", 315 | " 'ParaHeadAlign',\n", 316 | " 'ParaShape',\n", 317 | " 'ParentCtrl',\n", 318 | " 'Path',\n", 319 | " 'PicEffect',\n", 320 | " 'PlacementType',\n", 321 | " 'PointToHwpUnit',\n", 322 | " 'PresentEffect',\n", 323 | " 'PrintDevice',\n", 324 | " 'PrintPaper',\n", 325 | " 'PrintRange',\n", 326 | " 'PrintType',\n", 327 | " 'ProtectPrivateInfo',\n", 328 | " 'PutFieldText',\n", 329 | " 'PutMetatagNameText',\n", 330 | " 'Quit',\n", 331 | " 'RGBColor',\n", 332 | " 'RegisterModule',\n", 333 | " 'RegisterPrivateInfoPattern',\n", 334 | " 'ReleaseAction',\n", 335 | " 'ReleaseScan',\n", 336 | " 'RenameField',\n", 337 | " 'RenameMetatag',\n", 338 | " 'ReplaceAction',\n", 339 | " 'ReplaceFont',\n", 340 | " 'Revision',\n", 341 | " 'Run',\n", 342 | " 'RunScriptMacro',\n", 343 | " 'Save',\n", 344 | " 'SaveAs',\n", 345 | " 'ScanFont',\n", 346 | " 'SelectText',\n", 347 | " 'SelectionMode',\n", 348 | " 'SetBarCodeImage',\n", 349 | " 'SetCurFieldName',\n", 350 | " 'SetCurMetatagName',\n", 351 | " 'SetDRMAuthority',\n", 352 | " 'SetFieldViewOption',\n", 353 | " 'SetMessageBoxMode',\n", 354 | " 'SetPos',\n", 355 | " 'SetPosBySet',\n", 356 | " 'SetPrivateInfoPassword',\n", 357 | " 'SetTextFile',\n", 358 | " 'SetTitleName',\n", 359 | " 'SetUserInfo',\n", 360 | " 'SideType',\n", 361 | " 'Signature',\n", 362 | " 'Slash',\n", 363 | " 'SolarToLunar',\n", 364 | " 'SolarToLunarBySet',\n", 365 | " 'SortDelimiter',\n", 366 | " 'StrikeOut',\n", 367 | " 'StyleType',\n", 368 | " 'SubtPos',\n", 369 | " 'TableBreak',\n", 370 | " 'TableFormat',\n", 371 | " 'TableSwapType',\n", 372 | " 'TableTarget',\n", 373 | " 'TextAlign',\n", 374 | " 'TextArtAlign',\n", 375 | " 'TextDir',\n", 376 | " 'TextFlowType',\n", 377 | " 'TextWrapType',\n", 378 | " 'UnSelectCtrl',\n", 379 | " 'VAlign',\n", 380 | " 'Version',\n", 381 | " 'VertRel',\n", 382 | " 'ViewFlag',\n", 383 | " 'ViewProperties',\n", 384 | " 'WatermarkBrush',\n", 385 | " 'WidthRel',\n", 386 | " 'XHwpDocuments',\n", 387 | " 'XHwpMessageBox',\n", 388 | " 'XHwpODBC',\n", 389 | " 'XHwpWindows',\n", 390 | " '_ApplyTypes_',\n", 391 | " '__class__',\n", 392 | " '__delattr__',\n", 393 | " '__dict__',\n", 394 | " '__dir__',\n", 395 | " '__doc__',\n", 396 | " '__eq__',\n", 397 | " '__format__',\n", 398 | " '__ge__',\n", 399 | " '__getattr__',\n", 400 | " '__getattribute__',\n", 401 | " '__gt__',\n", 402 | " '__hash__',\n", 403 | " '__init__',\n", 404 | " '__init_subclass__',\n", 405 | " '__iter__',\n", 406 | " '__le__',\n", 407 | " '__lt__',\n", 408 | " '__module__',\n", 409 | " '__ne__',\n", 410 | " '__new__',\n", 411 | " '__reduce__',\n", 412 | " '__reduce_ex__',\n", 413 | " '__repr__',\n", 414 | " '__setattr__',\n", 415 | " '__sizeof__',\n", 416 | " '__str__',\n", 417 | " '__subclasshook__',\n", 418 | " '__weakref__',\n", 419 | " '_get_good_object_',\n", 420 | " '_get_good_single_object_',\n", 421 | " '_oleobj_',\n", 422 | " '_prop_map_get_',\n", 423 | " '_prop_map_put_',\n", 424 | " 'coclass_clsid']" 425 | ] 426 | }, 427 | "execution_count": 46, 428 | "metadata": {}, 429 | "output_type": "execute_result" 430 | } 431 | ], 432 | "source": [ 433 | "dir(app.api)" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 62, 439 | "metadata": { 440 | "language": "python" 441 | }, 442 | "outputs": [ 443 | { 444 | "data": { 445 | "text/plain": [ 446 | "(True, 1, 1, 1, 1, 3, 11, 0, '')" 447 | ] 448 | }, 449 | "execution_count": 62, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "app.api.KeyIndicator()" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 63, 461 | "metadata": { 462 | "language": "python" 463 | }, 464 | "outputs": [ 465 | { 466 | "data": { 467 | "text/plain": [ 468 | "(True, 1, 1, 1, 1, 1, 3, 0, '(C7): 문자 입력')" 469 | ] 470 | }, 471 | "execution_count": 63, 472 | "metadata": {}, 473 | "output_type": "execute_result" 474 | } 475 | ], 476 | "source": [ 477 | "app.api.KeyIndicator()" 478 | ] 479 | }, 480 | { 481 | "cell_type": "code", 482 | "execution_count": 69, 483 | "metadata": { 484 | "language": "python" 485 | }, 486 | "outputs": [ 487 | { 488 | "data": { 489 | "text/plain": [ 490 | "['CLSID',\n", 491 | " 'CtrlCh',\n", 492 | " 'CtrlID',\n", 493 | " 'GetAnchorPos',\n", 494 | " 'HasList',\n", 495 | " 'Next',\n", 496 | " 'Prev',\n", 497 | " 'Properties',\n", 498 | " 'UserDesc',\n", 499 | " '_ApplyTypes_',\n", 500 | " '__class__',\n", 501 | " '__delattr__',\n", 502 | " '__dict__',\n", 503 | " '__dir__',\n", 504 | " '__doc__',\n", 505 | " '__eq__',\n", 506 | " '__format__',\n", 507 | " '__ge__',\n", 508 | " '__getattr__',\n", 509 | " '__getattribute__',\n", 510 | " '__gt__',\n", 511 | " '__hash__',\n", 512 | " '__init__',\n", 513 | " '__init_subclass__',\n", 514 | " '__iter__',\n", 515 | " '__le__',\n", 516 | " '__lt__',\n", 517 | " '__module__',\n", 518 | " '__ne__',\n", 519 | " '__new__',\n", 520 | " '__reduce__',\n", 521 | " '__reduce_ex__',\n", 522 | " '__repr__',\n", 523 | " '__setattr__',\n", 524 | " '__sizeof__',\n", 525 | " '__str__',\n", 526 | " '__subclasshook__',\n", 527 | " '__weakref__',\n", 528 | " '_get_good_object_',\n", 529 | " '_get_good_single_object_',\n", 530 | " '_oleobj_',\n", 531 | " '_prop_map_get_',\n", 532 | " '_prop_map_put_',\n", 533 | " 'coclass_clsid']" 534 | ] 535 | }, 536 | "execution_count": 69, 537 | "metadata": {}, 538 | "output_type": "execute_result" 539 | } 540 | ], 541 | "source": [ 542 | "dir(app.api.ParentCtrl)" 543 | ] 544 | } 545 | ], 546 | "metadata": { 547 | "kernelspec": { 548 | "display_name": "Python 3", 549 | "language": "python", 550 | "name": "python3" 551 | }, 552 | "language_info": { 553 | "codemirror_mode": { 554 | "name": "ipython", 555 | "version": 3 556 | }, 557 | "file_extension": ".py", 558 | "mimetype": "text/x-python", 559 | "name": "python", 560 | "nbconvert_exporter": "python", 561 | "pygments_lexer": "ipython3", 562 | "version": "3.10.1" 563 | }, 564 | "orig_nbformat": 4, 565 | "widgets": { 566 | "application/vnd.jupyter.widget-state+json": { 567 | "state": {}, 568 | "version_major": 2, 569 | "version_minor": 0 570 | } 571 | } 572 | }, 573 | "nbformat": 4, 574 | "nbformat_minor": 2 575 | } 576 | -------------------------------------------------------------------------------- /_proc/02_api/01_actions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c4da61aa-5edc-4ac8-a771-f1a2ffc6f0bb", 6 | "metadata": {}, 7 | "source": [ 8 | "---\n", 9 | "description: action list\n", 10 | "output-file: actions.html\n", 11 | "title: actions\n", 12 | "\n", 13 | "---" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "5bfcb83c-6631-420e-97f3-1400a291e5af", 27 | "metadata": { 28 | "language": "python" 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 1, 36 | "id": "0f1b3536-268c-4a87-9c14-153b6aeeef4c", 37 | "metadata": { 38 | "language": "python" 39 | }, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/markdown": [ 44 | "---\n", 45 | "\n", 46 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L896){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 47 | "\n", 48 | "### _Action\n", 49 | "\n", 50 | "> _Action (app, action_key:str, pset_key=None)\n", 51 | "\n", 52 | "한글 Action 클래스 입니다. 엑션의 기능을 사용하기 쉽게 만들고자 했습니다." 53 | ], 54 | "text/plain": [ 55 | "---\n", 56 | "\n", 57 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L896){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 58 | "\n", 59 | "### _Action\n", 60 | "\n", 61 | "> _Action (app, action_key:str, pset_key=None)\n", 62 | "\n", 63 | "한글 Action 클래스 입니다. 엑션의 기능을 사용하기 쉽게 만들고자 했습니다." 64 | ] 65 | }, 66 | "execution_count": 1, 67 | "metadata": {}, 68 | "output_type": "execute_result" 69 | } 70 | ], 71 | "source": [ 72 | "#| echo: false\n", 73 | "#| output: asis\n", 74 | "show_doc(_Action)" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "id": "46c41f75", 81 | "metadata": { 82 | "language": "python" 83 | }, 84 | "outputs": [], 85 | "source": [] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 2, 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "data": { 94 | "text/markdown": [ 95 | "---\n", 96 | "\n", 97 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L937){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 98 | "\n", 99 | "### create_action_wrapper\n", 100 | "\n", 101 | "> create_action_wrapper (app, action_key, pset_key=None)" 102 | ], 103 | "text/plain": [ 104 | "---\n", 105 | "\n", 106 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L937){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 107 | "\n", 108 | "### create_action_wrapper\n", 109 | "\n", 110 | "> create_action_wrapper (app, action_key, pset_key=None)" 111 | ] 112 | }, 113 | "execution_count": 2, 114 | "metadata": {}, 115 | "output_type": "execute_result" 116 | } 117 | ], 118 | "source": [ 119 | "#| echo: false\n", 120 | "#| output: asis\n", 121 | "show_doc(create_action_wrapper)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 3, 127 | "id": "94f73932-e45f-4871-ac8c-a3dabd8f9aca", 128 | "metadata": { 129 | "language": "python" 130 | }, 131 | "outputs": [ 132 | { 133 | "data": { 134 | "text/markdown": [ 135 | "---\n", 136 | "\n", 137 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L943){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 138 | "\n", 139 | "### _Actions\n", 140 | "\n", 141 | "> _Actions (app)\n", 142 | "\n", 143 | "사전에 정보가 확인된 Action들을 App에 등록하는 기능을 합니다." 144 | ], 145 | "text/plain": [ 146 | "---\n", 147 | "\n", 148 | "[source](https://github.com/JunDamin/hwpapi/blob/main/hwpapi/actions.py#L943){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", 149 | "\n", 150 | "### _Actions\n", 151 | "\n", 152 | "> _Actions (app)\n", 153 | "\n", 154 | "사전에 정보가 확인된 Action들을 App에 등록하는 기능을 합니다." 155 | ] 156 | }, 157 | "execution_count": 3, 158 | "metadata": {}, 159 | "output_type": "execute_result" 160 | } 161 | ], 162 | "source": [ 163 | "#| echo: false\n", 164 | "#| output: asis\n", 165 | "show_doc(_Actions)" 166 | ] 167 | } 168 | ], 169 | "metadata": { 170 | "kernelspec": { 171 | "display_name": "python3", 172 | "language": "python", 173 | "name": "python3" 174 | }, 175 | "language_info": { 176 | "codemirror_mode": { 177 | "name": "ipython", 178 | "version": 3 179 | }, 180 | "file_extension": ".py", 181 | "mimetype": "text/x-python", 182 | "name": "python", 183 | "nbconvert_exporter": "python", 184 | "pygments_lexer": "ipython3", 185 | "version": "3.10.1" 186 | }, 187 | "widgets": { 188 | "application/vnd.jupyter.widget-state+json": { 189 | "state": {}, 190 | "version_major": 2, 191 | "version_minor": 0 192 | } 193 | } 194 | }, 195 | "nbformat": 4, 196 | "nbformat_minor": 5 197 | } 198 | -------------------------------------------------------------------------------- /_proc/02_api/04_constants.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "71fff6a6-96df-43f3-90af-f5be48acfe38", 6 | "metadata": {}, 7 | "source": [ 8 | "---\n", 9 | "description: constants\n", 10 | "output-file: constants.html\n", 11 | "title: constants\n", 12 | "---" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "" 20 | ] 21 | } 22 | ], 23 | "metadata": { 24 | "kernelspec": { 25 | "display_name": "python3", 26 | "language": "python", 27 | "name": "python3" 28 | }, 29 | "widgets": { 30 | "application/vnd.jupyter.widget-state+json": { 31 | "state": {}, 32 | "version_major": 2, 33 | "version_minor": 0 34 | } 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /_proc/02_api/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/_proc/02_api/test.pdf -------------------------------------------------------------------------------- /_proc/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | format: 5 | html: 6 | theme: darkly 7 | css: styles.css 8 | toc: true 9 | 10 | website: 11 | twitter-card: true 12 | open-graph: true 13 | repo-actions: [issue] 14 | navbar: 15 | background: primary 16 | search: true 17 | sidebar: 18 | style: floating 19 | 20 | metadata-files: [nbdev.yml, sidebar.yml] -------------------------------------------------------------------------------- /_proc/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "raw", 5 | "metadata": {}, 6 | "source": [ 7 | "---\n", 8 | "description: python wrapper for HWPFrame.HwpObject using win32com\n", 9 | "output-file: index.html\n", 10 | "title: HwpApi\n", 11 | "\n", 12 | "---\n", 13 | "\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "여기서 [Tutorials](https://jundamin.github.io/hwpapi/)을 볼 수 있습니다." 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "## Install" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "이 패키지는 win32com을 통해 좀더 쉽게 한글 자동화를 하기 위한 패키지 입니다.\n", 42 | "따라서, 한글 오피스가 설치된 Windows에서만 작동합니다.\n", 43 | "리눅스나 한컴 오피스가 설치된 Mac OS에서는 작동하지 않습니다." 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "다른 일반적인 패키지와 같이 아래 명령어를 입력하면 설치할 수 있습니다." 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "```sh\n", 58 | "pip install hwpapi\n", 59 | "```" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## How to use" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "기본적으로는 wi32com을 통한 한컴 오피스 자동화를 보다 쉽게 사용하기 위해 개발한 패키지 입니다. \n", 74 | "\n", 75 | "기존의 연동성을 최대한 유지하면서도 파이써닉하게 코드를 짤 수 있도록 개선하고자 하였습니다.\n", 76 | "\n", 77 | "[nbdev](https://nbdev.fast.ai/)에서 권장하는 스타일로 작성되다보니 jupyter notebook이나 jupyter lab에서는 자동완성이 잘 작동되지만, VS Code에서는 자동완성이 작동 안할 수 있습니다." 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "### 기존 코드와 연동성 비교하기\n", 85 | "\n", 86 | "[회사원 코딩](https://employeecoding.tistory.com/72)에 가보시면 아래와 같이 자동화 코드가 있습니다. \n", 87 | "\n", 88 | "```python\n", 89 | "import win32com.client as win32\n", 90 | "hwp = win32.gencache.EnsureDispatch(\"HWPFrame.HwpObject\")\n", 91 | "hwp.XHwpWindows.Item(0).Visible = True\n", 92 | "\n", 93 | "act = hwp.CreateAction(\"InsertText\")\n", 94 | "pset = act.CreateSet()\n", 95 | "pset.SetItem(\"Text\", \"Hello\\r\\nWorld!\")\n", 96 | "act.Execute(pset)\n", 97 | "```\n", 98 | "\n", 99 | "이 코드는 기본적으로 장황하다고 볼 만한 상황입니다.\n", 100 | "이 코드를 `HwpApi`를 사용하면 아래와 같이 간결하게 정리가 됨을 볼 수 있습니다." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": { 107 | "language": "python" 108 | }, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "True" 114 | ] 115 | }, 116 | "execution_count": null, 117 | "metadata": {}, 118 | "output_type": "execute_result" 119 | } 120 | ], 121 | "source": [ 122 | "from hwpapi.core import App\n", 123 | "\n", 124 | "app = App()\n", 125 | "action = app.actions.InsertText()\n", 126 | "p = action.pset\n", 127 | "p.Text = \"Hello\\r\\nWorld!\"\n", 128 | "action.run()" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "이렇게 자주 사용하는 기능은 함수로 만들었습니다." 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": { 142 | "language": "python" 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "app.insert_text(\"Hello\\r\\nWorld!\")" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "글자 모양을 바꾸는 것은 자주 있는 함수 입니다.\n", 154 | "win32com을 사용하면 아래와 같이 작성해야 합니다.\n", 155 | "\n", 156 | "```python\n", 157 | "Act = hwp.CreateAction(\"CharShape\")\n", 158 | "Set = Act.CreateSet()\n", 159 | "Act.GetDefault(Set) \n", 160 | "Set.Item(\"Italic\")\n", 161 | "Set.SetItem(\"Italic\", 1)\n", 162 | "Act.Execute(Set)\n", 163 | "```\n", 164 | "\n", 165 | "이렇게 자주 사용되는 기능은 함수로 만들어 사용할 수 있게 했습니다." 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": { 172 | "language": "python" 173 | }, 174 | "outputs": [ 175 | { 176 | "data": { 177 | "text/plain": [ 178 | "True" 179 | ] 180 | }, 181 | "execution_count": null, 182 | "metadata": {}, 183 | "output_type": "execute_result" 184 | } 185 | ], 186 | "source": [ 187 | "app.set_charshape(italic=True)" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "코드를 보시면 hwp를 세팅하는 부분이 간략해졌습니다.\n", 195 | "또한 파라미터 설정이 파이썬 객체처럼 설정할 수 있게 변경 되어 있는 것을 볼 수 있습니다.\n", 196 | "\n", 197 | "이런 식으로 파이썬에서 사용하기 쉽게 만들었습니다." 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "metadata": {}, 203 | "source": [ 204 | "## 왜 HwpApi를 만들었나요?\n", 205 | "\n", 206 | "가장 큰 이유는 스스로 사용하기 위해서 입니다.\n", 207 | "직장인으로 많은 한글 문서를 편집하고 작성하곤 하는데 단순 반복업무가 너무 많다는 것이 불만이었습니다.\n", 208 | "이런 문제를 해결하는 방법으로 한글 자동화에 대한 이야기를 파이콘에서 보게 되었습니다.\n", 209 | "특히 '회사원 코딩' 님의 블로그와 영상이 많은 참조가 되었습니다.\n", 210 | "\n", 211 | "다만 그 과정에서 설명자료가 부족하기도 하고 예전에 작성했던 코드들을 자꾸 찾아보게 되면서 아래아 한글 용 파이썬 패키지가 있으면 좋겠다는 생각을 했습니다.\n", 212 | "특히 업무를 하면서 엑셀 자동화를 위해 xlwings를 사용해 보면서 파이썬으로 사용하기 쉽게 만든 라이브러리가 코딩 작업 효율을 엄청 올린다는 것을 깨닫게 되었습니다.\n", 213 | "\n", 214 | "제출 마감까지 해야 할 일들을 빠르게 하기 위해서 빠르게 한글 자동화가 된다면 좋겠다는 생각으로 만들게 되었습니다.\n", 215 | "\n", 216 | "기본적인 철학은 xlwings을 따라하고 있습니다. 기본적으로는 자주 쓰이는 항목들을 사용하기 쉽게 정리한 메소드 등으로 구현하고, 부족한 부분은 [`App.api`](https://JunDamin.github.io/hwpapi/02_api/core.html#app.api)형태로 `win32com`으로 하는 것과 동일한 작업이 가능하게 하여 한글 api의 모든 기능을 사용할 수 있도록 구현하였습니다.\n", 217 | "\n", 218 | "메소드로 만드는 것에는 아직 고민이 있습니다. chain과 같은 형태로 여러가지 콤비네이션을 사전에 세팅을 해야 하나 싶은 부분도 있고 실제로 유용하게 사용할 수 있는 여러가지 아이템 등도 있어서 어떤 부분까지 이 패키지에 구현할지는 고민하고 있습니다.\n", 219 | "\n", 220 | "다만 이런 형태의 작업을 통해서 어쩌면 hwp api wrapper가 활성화 되어서 단순 작업을 자동화 할 수 있기를 바라고 있습니다." 221 | ] 222 | } 223 | ], 224 | "metadata": { 225 | "kernelspec": { 226 | "display_name": "python3", 227 | "language": "python", 228 | "name": "python3" 229 | }, 230 | "widgets": { 231 | "application/vnd.jupyter.widget-state+json": { 232 | "state": {}, 233 | "version_major": 2, 234 | "version_minor": 0 235 | } 236 | } 237 | }, 238 | "nbformat": 4, 239 | "nbformat_minor": 4 240 | } 241 | -------------------------------------------------------------------------------- /action_table/Action Table_modified.htm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/action_table/Action Table_modified.htm -------------------------------------------------------------------------------- /docs/01_tutorials/img/분양가상한제1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/분양가상한제1.png -------------------------------------------------------------------------------- /docs/01_tutorials/img/분양가상한제2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/분양가상한제2.png -------------------------------------------------------------------------------- /docs/01_tutorials/img/시범사업지.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/시범사업지.png -------------------------------------------------------------------------------- /docs/01_tutorials/img/시범사업지_변경.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/시범사업지_변경.png -------------------------------------------------------------------------------- /docs/01_tutorials/img/주거안정.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/주거안정.png -------------------------------------------------------------------------------- /docs/01_tutorials/img/주거안정_변경.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/01_tutorials/img/주거안정_변경.png -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | Sitemap: https://JunDamin.github.io/hwpapi/sitemap.xml 2 | -------------------------------------------------------------------------------- /docs/site_libs/bootstrap/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/docs/site_libs/bootstrap/bootstrap-icons.woff -------------------------------------------------------------------------------- /docs/site_libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); 9 | // @license-end -------------------------------------------------------------------------------- /docs/site_libs/quarto-html/popper.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @popperjs/core v2.11.7 - MIT License 3 | */ 4 | 5 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); 6 | 7 | -------------------------------------------------------------------------------- /docs/site_libs/quarto-html/quarto-syntax-highlighting-dark.css: -------------------------------------------------------------------------------- 1 | /* quarto syntax highlight colors */ 2 | :root { 3 | --quarto-hl-al-color: #f07178; 4 | --quarto-hl-an-color: #d4d0ab; 5 | --quarto-hl-at-color: #00e0e0; 6 | --quarto-hl-bn-color: #d4d0ab; 7 | --quarto-hl-bu-color: #abe338; 8 | --quarto-hl-ch-color: #abe338; 9 | --quarto-hl-co-color: #f8f8f2; 10 | --quarto-hl-cv-color: #ffd700; 11 | --quarto-hl-cn-color: #ffd700; 12 | --quarto-hl-cf-color: #ffa07a; 13 | --quarto-hl-dt-color: #ffa07a; 14 | --quarto-hl-dv-color: #d4d0ab; 15 | --quarto-hl-do-color: #f8f8f2; 16 | --quarto-hl-er-color: #f07178; 17 | --quarto-hl-ex-color: #00e0e0; 18 | --quarto-hl-fl-color: #d4d0ab; 19 | --quarto-hl-fu-color: #ffa07a; 20 | --quarto-hl-im-color: #abe338; 21 | --quarto-hl-in-color: #d4d0ab; 22 | --quarto-hl-kw-color: #ffa07a; 23 | --quarto-hl-op-color: #ffa07a; 24 | --quarto-hl-ot-color: #00e0e0; 25 | --quarto-hl-pp-color: #dcc6e0; 26 | --quarto-hl-re-color: #00e0e0; 27 | --quarto-hl-sc-color: #abe338; 28 | --quarto-hl-ss-color: #abe338; 29 | --quarto-hl-st-color: #abe338; 30 | --quarto-hl-va-color: #00e0e0; 31 | --quarto-hl-vs-color: #abe338; 32 | --quarto-hl-wa-color: #dcc6e0; 33 | } 34 | 35 | /* other quarto variables */ 36 | :root { 37 | --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 38 | } 39 | 40 | code span.al { 41 | background-color: #2a0f15; 42 | font-weight: bold; 43 | color: #f07178; 44 | } 45 | 46 | code span.an { 47 | color: #d4d0ab; 48 | } 49 | 50 | code span.at { 51 | color: #00e0e0; 52 | } 53 | 54 | code span.bn { 55 | color: #d4d0ab; 56 | } 57 | 58 | code span.bu { 59 | color: #abe338; 60 | } 61 | 62 | code span.ch { 63 | color: #abe338; 64 | } 65 | 66 | code span.co { 67 | font-style: italic; 68 | color: #f8f8f2; 69 | } 70 | 71 | code span.cv { 72 | color: #ffd700; 73 | } 74 | 75 | code span.cn { 76 | color: #ffd700; 77 | } 78 | 79 | code span.cf { 80 | font-weight: bold; 81 | color: #ffa07a; 82 | } 83 | 84 | code span.dt { 85 | color: #ffa07a; 86 | } 87 | 88 | code span.dv { 89 | color: #d4d0ab; 90 | } 91 | 92 | code span.do { 93 | color: #f8f8f2; 94 | } 95 | 96 | code span.er { 97 | color: #f07178; 98 | text-decoration: underline; 99 | } 100 | 101 | code span.ex { 102 | font-weight: bold; 103 | color: #00e0e0; 104 | } 105 | 106 | code span.fl { 107 | color: #d4d0ab; 108 | } 109 | 110 | code span.fu { 111 | color: #ffa07a; 112 | } 113 | 114 | code span.im { 115 | color: #abe338; 116 | } 117 | 118 | code span.in { 119 | color: #d4d0ab; 120 | } 121 | 122 | code span.kw { 123 | font-weight: bold; 124 | color: #ffa07a; 125 | } 126 | 127 | pre > code.sourceCode > span { 128 | color: #f8f8f2; 129 | } 130 | 131 | code span { 132 | color: #f8f8f2; 133 | } 134 | 135 | code.sourceCode > span { 136 | color: #f8f8f2; 137 | } 138 | 139 | div.sourceCode, 140 | div.sourceCode pre.sourceCode { 141 | color: #f8f8f2; 142 | } 143 | 144 | code span.op { 145 | color: #ffa07a; 146 | } 147 | 148 | code span.ot { 149 | color: #00e0e0; 150 | } 151 | 152 | code span.pp { 153 | color: #dcc6e0; 154 | } 155 | 156 | code span.re { 157 | background-color: #f8f8f2; 158 | color: #00e0e0; 159 | } 160 | 161 | code span.sc { 162 | color: #abe338; 163 | } 164 | 165 | code span.ss { 166 | color: #abe338; 167 | } 168 | 169 | code span.st { 170 | color: #abe338; 171 | } 172 | 173 | code span.va { 174 | color: #00e0e0; 175 | } 176 | 177 | code span.vs { 178 | color: #abe338; 179 | } 180 | 181 | code span.wa { 182 | color: #dcc6e0; 183 | } 184 | 185 | .prevent-inlining { 186 | content: ".tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} -------------------------------------------------------------------------------- /docs/site_libs/quarto-nav/headroom.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it 3 | * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js 4 | * License: MIT 5 | */ 6 | 7 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=ls.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s}); 8 | -------------------------------------------------------------------------------- /docs/site_libs/quarto-nav/quarto-nav.js: -------------------------------------------------------------------------------- 1 | const headroomChanged = new CustomEvent("quarto-hrChanged", { 2 | detail: {}, 3 | bubbles: true, 4 | cancelable: false, 5 | composed: false, 6 | }); 7 | 8 | window.document.addEventListener("DOMContentLoaded", function () { 9 | let init = false; 10 | 11 | // Manage the back to top button, if one is present. 12 | let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; 13 | const scrollDownBuffer = 5; 14 | const scrollUpBuffer = 35; 15 | const btn = document.getElementById("quarto-back-to-top"); 16 | const hideBackToTop = () => { 17 | btn.style.display = "none"; 18 | }; 19 | const showBackToTop = () => { 20 | btn.style.display = "inline-block"; 21 | }; 22 | if (btn) { 23 | window.document.addEventListener( 24 | "scroll", 25 | function () { 26 | const currentScrollTop = 27 | window.pageYOffset || document.documentElement.scrollTop; 28 | 29 | // Shows and hides the button 'intelligently' as the user scrolls 30 | if (currentScrollTop - scrollDownBuffer > lastScrollTop) { 31 | hideBackToTop(); 32 | lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; 33 | } else if (currentScrollTop < lastScrollTop - scrollUpBuffer) { 34 | showBackToTop(); 35 | lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; 36 | } 37 | 38 | // Show the button at the bottom, hides it at the top 39 | if (currentScrollTop <= 0) { 40 | hideBackToTop(); 41 | } else if ( 42 | window.innerHeight + currentScrollTop >= 43 | document.body.offsetHeight 44 | ) { 45 | showBackToTop(); 46 | } 47 | }, 48 | false 49 | ); 50 | } 51 | 52 | function throttle(func, wait) { 53 | var timeout; 54 | return function () { 55 | const context = this; 56 | const args = arguments; 57 | const later = function () { 58 | clearTimeout(timeout); 59 | timeout = null; 60 | func.apply(context, args); 61 | }; 62 | 63 | if (!timeout) { 64 | timeout = setTimeout(later, wait); 65 | } 66 | }; 67 | } 68 | 69 | function headerOffset() { 70 | // Set an offset if there is are fixed top navbar 71 | const headerEl = window.document.querySelector("header.fixed-top"); 72 | if (headerEl) { 73 | return headerEl.clientHeight; 74 | } else { 75 | return 0; 76 | } 77 | } 78 | 79 | function footerOffset() { 80 | const footerEl = window.document.querySelector("footer.footer"); 81 | if (footerEl) { 82 | return footerEl.clientHeight; 83 | } else { 84 | return 0; 85 | } 86 | } 87 | 88 | function dashboardOffset() { 89 | const dashboardNavEl = window.document.getElementById( 90 | "quarto-dashboard-header" 91 | ); 92 | if (dashboardNavEl !== null) { 93 | return dashboardNavEl.clientHeight; 94 | } else { 95 | return 0; 96 | } 97 | } 98 | 99 | function updateDocumentOffsetWithoutAnimation() { 100 | updateDocumentOffset(false); 101 | } 102 | 103 | function updateDocumentOffset(animated) { 104 | // set body offset 105 | const topOffset = headerOffset(); 106 | const bodyOffset = topOffset + footerOffset() + dashboardOffset(); 107 | const bodyEl = window.document.body; 108 | bodyEl.setAttribute("data-bs-offset", topOffset); 109 | bodyEl.style.paddingTop = topOffset + "px"; 110 | 111 | // deal with sidebar offsets 112 | const sidebars = window.document.querySelectorAll( 113 | ".sidebar, .headroom-target" 114 | ); 115 | sidebars.forEach((sidebar) => { 116 | if (!animated) { 117 | sidebar.classList.add("notransition"); 118 | // Remove the no transition class after the animation has time to complete 119 | setTimeout(function () { 120 | sidebar.classList.remove("notransition"); 121 | }, 201); 122 | } 123 | 124 | if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { 125 | sidebar.style.top = "0"; 126 | sidebar.style.maxHeight = "100vh"; 127 | } else { 128 | sidebar.style.top = topOffset + "px"; 129 | sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; 130 | } 131 | }); 132 | 133 | // allow space for footer 134 | const mainContainer = window.document.querySelector(".quarto-container"); 135 | if (mainContainer) { 136 | mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; 137 | } 138 | 139 | // link offset 140 | let linkStyle = window.document.querySelector("#quarto-target-style"); 141 | if (!linkStyle) { 142 | linkStyle = window.document.createElement("style"); 143 | linkStyle.setAttribute("id", "quarto-target-style"); 144 | window.document.head.appendChild(linkStyle); 145 | } 146 | while (linkStyle.firstChild) { 147 | linkStyle.removeChild(linkStyle.firstChild); 148 | } 149 | if (topOffset > 0) { 150 | linkStyle.appendChild( 151 | window.document.createTextNode(` 152 | section:target::before { 153 | content: ""; 154 | display: block; 155 | height: ${topOffset}px; 156 | margin: -${topOffset}px 0 0; 157 | }`) 158 | ); 159 | } 160 | if (init) { 161 | window.dispatchEvent(headroomChanged); 162 | } 163 | init = true; 164 | } 165 | 166 | // initialize headroom 167 | var header = window.document.querySelector("#quarto-header"); 168 | if (header && window.Headroom) { 169 | const headroom = new window.Headroom(header, { 170 | tolerance: 5, 171 | onPin: function () { 172 | const sidebars = window.document.querySelectorAll( 173 | ".sidebar, .headroom-target" 174 | ); 175 | sidebars.forEach((sidebar) => { 176 | sidebar.classList.remove("sidebar-unpinned"); 177 | }); 178 | updateDocumentOffset(); 179 | }, 180 | onUnpin: function () { 181 | const sidebars = window.document.querySelectorAll( 182 | ".sidebar, .headroom-target" 183 | ); 184 | sidebars.forEach((sidebar) => { 185 | sidebar.classList.add("sidebar-unpinned"); 186 | }); 187 | updateDocumentOffset(); 188 | }, 189 | }); 190 | headroom.init(); 191 | 192 | let frozen = false; 193 | window.quartoToggleHeadroom = function () { 194 | if (frozen) { 195 | headroom.unfreeze(); 196 | frozen = false; 197 | } else { 198 | headroom.freeze(); 199 | frozen = true; 200 | } 201 | }; 202 | } 203 | 204 | window.addEventListener( 205 | "hashchange", 206 | function (e) { 207 | if ( 208 | getComputedStyle(document.documentElement).scrollBehavior !== "smooth" 209 | ) { 210 | window.scrollTo(0, window.pageYOffset - headerOffset()); 211 | } 212 | }, 213 | false 214 | ); 215 | 216 | // Observe size changed for the header 217 | const headerEl = window.document.querySelector("header.fixed-top"); 218 | if (headerEl && window.ResizeObserver) { 219 | const observer = new window.ResizeObserver(() => { 220 | setTimeout(updateDocumentOffsetWithoutAnimation, 0); 221 | }); 222 | observer.observe(headerEl, { 223 | attributes: true, 224 | childList: true, 225 | characterData: true, 226 | }); 227 | } else { 228 | window.addEventListener( 229 | "resize", 230 | throttle(updateDocumentOffsetWithoutAnimation, 50) 231 | ); 232 | } 233 | setTimeout(updateDocumentOffsetWithoutAnimation, 250); 234 | 235 | // fixup index.html links if we aren't on the filesystem 236 | if (window.location.protocol !== "file:") { 237 | const links = window.document.querySelectorAll("a"); 238 | for (let i = 0; i < links.length; i++) { 239 | if (links[i].href) { 240 | links[i].href = links[i].href.replace(/\/index\.html/, "/"); 241 | } 242 | } 243 | 244 | // Fixup any sharing links that require urls 245 | // Append url to any sharing urls 246 | const sharingLinks = window.document.querySelectorAll( 247 | "a.sidebar-tools-main-item, a.quarto-navigation-tool, a.quarto-navbar-tools, a.quarto-navbar-tools-item" 248 | ); 249 | for (let i = 0; i < sharingLinks.length; i++) { 250 | const sharingLink = sharingLinks[i]; 251 | const href = sharingLink.getAttribute("href"); 252 | if (href) { 253 | sharingLink.setAttribute( 254 | "href", 255 | href.replace("|url|", window.location.href) 256 | ); 257 | } 258 | } 259 | 260 | // Scroll the active navigation item into view, if necessary 261 | const navSidebar = window.document.querySelector("nav#quarto-sidebar"); 262 | if (navSidebar) { 263 | // Find the active item 264 | const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); 265 | if (activeItem) { 266 | // Wait for the scroll height and height to resolve by observing size changes on the 267 | // nav element that is scrollable 268 | const resizeObserver = new ResizeObserver((_entries) => { 269 | // The bottom of the element 270 | const elBottom = activeItem.offsetTop; 271 | const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; 272 | 273 | // The element height and scroll height are the same, then we are still loading 274 | if (viewBottom !== navSidebar.scrollHeight) { 275 | // Determine if the item isn't visible and scroll to it 276 | if (elBottom >= viewBottom) { 277 | navSidebar.scrollTop = elBottom; 278 | } 279 | 280 | // stop observing now since we've completed the scroll 281 | resizeObserver.unobserve(navSidebar); 282 | } 283 | }); 284 | resizeObserver.observe(navSidebar); 285 | } 286 | } 287 | } 288 | }); 289 | -------------------------------------------------------------------------------- /docs/site_libs/quarto-search/fuse.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) 3 | * 4 | * Copyright (c) 2022 Kiro Risk (http://kiro.me) 5 | * All Rights Reserved. Apache Software License 2.0 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | */ 9 | var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://JunDamin.github.io/hwpapi/index.html 5 | 2023-11-15T02:20:35.667Z 6 | 7 | 8 | https://JunDamin.github.io/hwpapi/02_api/dataclasses.html 9 | 2024-01-26T23:32:13.268Z 10 | 11 | 12 | https://JunDamin.github.io/hwpapi/02_api/actions.html 13 | 2024-01-26T23:32:13.562Z 14 | 15 | 16 | https://JunDamin.github.io/hwpapi/01_tutorials/find_replace.html 17 | 2024-01-26T23:32:13.099Z 18 | 19 | 20 | https://JunDamin.github.io/hwpapi/01_tutorials/tutorial.html 21 | 2023-11-15T02:20:35.669Z 22 | 23 | 24 | https://JunDamin.github.io/hwpapi/02_api/core.html 25 | 2024-01-26T23:32:16.298Z 26 | 27 | 28 | https://JunDamin.github.io/hwpapi/02_api/functions.html 29 | 2023-11-15T02:20:36.041Z 30 | 31 | 32 | https://JunDamin.github.io/hwpapi/02_api/constants.html 33 | 2023-11-15T02:20:35.666Z 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 5; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 5; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /nbs/01_tutorials/01_app_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1b19722e-b225-4464-919f-7d69e8aecb3d", 6 | "metadata": {}, 7 | "source": [ 8 | "---\n", 9 | "description: 사용 기초\n", 10 | "output-file: tutorial.html\n", 11 | "title: 튜토리얼\n", 12 | "---\n", 13 | "\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 72, 19 | "id": "5dfe2ff7-0f7b-4fae-8a1c-9a09a85cd389", 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "name": "stdout", 24 | "output_type": "stream", 25 | "text": [ 26 | "The autoreload extension is already loaded. To reload it, use:\n", 27 | " %reload_ext autoreload\n" 28 | ] 29 | } 30 | ], 31 | "source": [ 32 | "%load_ext autoreload\n", 33 | "%autoreload 2" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "85e1facc-2f64-4da1-8351-ba21d5298dda", 39 | "metadata": {}, 40 | "source": [ 41 | "한컴 오피스는 액션이라는 것을 통해서 다양한 문서 편집을 가능하게 하고 있습니다. 그런 편집을 파이썬에서 보다 쉽게 사용하기 위해 만든 라이브러리입니다.\n", 42 | "\n", 43 | "기본적인 컨셉은 win32com을 쉽게 사용할 수 있게 개편한 것입니다.\n", 44 | "\n", 45 | "문서 편집의 기본 기능인 \n", 46 | "1. 문장 입력하기\n", 47 | "2. 커서 이동하기\n", 48 | "2. 영역 선택하기\n", 49 | "2. 서식 변경하기\n", 50 | "3. 텍스트 가져오기 \n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 73, 56 | "id": "cccb95a9-5588-40fa-ade4-bb0357b5e2ea", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "from hwpapi.core import App" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "id": "fe377bfb-a6cf-41fc-bc37-35f76dfe2600", 66 | "metadata": {}, 67 | "source": [ 68 | "`App`은 기존 hwpapi를 wrapping 하여 보다 사용하기 쉽게 만드는 것을 목적으로 하고 있는 클래스 입니다.\n", 69 | "\n", 70 | "`hwpapi`는 한글 컨트롤을 조합해서 자주사용했던 기능을 구현하였습니다.\n", 71 | "또한, api 메소드를 통해 win32com의 방식으로 모두 사용할 수 있어 다양하게 사용할 수 있습니다.\n", 72 | "\n", 73 | "아래와 같이 App 객체를 생성하면 한글이 열립니다." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 74, 79 | "id": "e8f1fd20-f6e4-48ec-8cba-e4c7fcbd7109", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "app = App()" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "id": "0f9a7312-f991-4e4e-8e44-c0ebcd263863", 89 | "metadata": {}, 90 | "source": [ 91 | "한글을 조작할 때는 크게 `action`과 `hwpctrl method`로 나눌 수 있습니다.\n", 92 | "`action`은 파라미터를 설정하고 그것을 실행시켜서 작동하는 방식으로 사용자 단축키를 누르면 실행되는 명령에 가깝다면\n", 93 | "`hwpctrl method`는 한글 자동화 모듈이 제공하는 것으로 사용자 입력에서는 신경쓰지 않아도 될 부분들을 처리하는 것이라고 보시면 됩니다.\n", 94 | "\n", 95 | "[공식 개발 매뉴얼](https://www.hancom.com/board/devmanualList.do)에 여러 `action`과 `parameter`, `method`의 설명을 제공하고 있습니다.\n", 96 | "아쉽게도 문서가 잘 관리되고 있지는 않으며, `hwpctrl`의 `method`들을 보면 명시되어 있지 않은 기능들이 개발되어 있음을 알 수 있습니다. \n", 97 | "\n", 98 | "앞으로 개발해 나가면서 확인된 액션과 메소드 들을 정리해 나가고자 합니다." 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "id": "a1807076-daf6-4a69-9a44-b2544f6dfa53", 104 | "metadata": {}, 105 | "source": [ 106 | "`action`은 `app`에서 생성할 수 있습니다.\n", 107 | "방식은 크게 2가지로 직접 `action key`를 입력하는 방법과 `actions`에 있는 `action` 객체를 생성하는 방법이 있습니다.\n", 108 | "아래 두 방식은 동일한 `Action` 객체를 생성합니다." 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 75, 114 | "id": "3baa1486-30bd-403e-b6c1-e273f18af05e", 115 | "metadata": {}, 116 | "outputs": [ 117 | { 118 | "data": { 119 | "text/plain": [ 120 | "" 121 | ] 122 | }, 123 | "execution_count": 75, 124 | "metadata": {}, 125 | "output_type": "execute_result" 126 | } 127 | ], 128 | "source": [ 129 | "action1 = app.create_action(\"InsertText\")\n", 130 | "action1" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 76, 136 | "id": "0163beab-1a00-4503-bb20-59230682fcec", 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "data": { 141 | "text/plain": [ 142 | "" 143 | ] 144 | }, 145 | "execution_count": 76, 146 | "metadata": {}, 147 | "output_type": "execute_result" 148 | } 149 | ], 150 | "source": [ 151 | "action2 = app.actions.InsertText()\n", 152 | "action2" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "1687251c-d10e-48ee-b26f-679b71b8f7b0", 158 | "metadata": {}, 159 | "source": [ 160 | "`Delete`(지우기), `BreakPara`(줄바꿈) 등 많은 `action`은 파라미터 세팅이 필요 없습니다.\n", 161 | "하지만 위에서 생성한 `InsertText`객체는 입력할 텍스트 값을 넣어주어야 합니다.\n", 162 | "이는 `parameter`를 통해 설정할 수 있습니다." 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 77, 168 | "id": "803629f4-a4c1-4a8b-b7e0-622619749568", 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "action1.pset.Text = \"입력하기\"" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "d26d8406-c324-43c3-a0e6-cc5e3c1f019d", 178 | "metadata": {}, 179 | "source": [ 180 | "파라미터를 설정한 후 다음과 같이 액션을 실행하면 텍스트가 입력되는 걸 볼 수 있습니다." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 78, 186 | "id": "3b7aa7f1-009f-4b23-a61a-dd153db363e0", 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "data": { 191 | "text/plain": [ 192 | "True" 193 | ] 194 | }, 195 | "execution_count": 78, 196 | "metadata": {}, 197 | "output_type": "execute_result" 198 | } 199 | ], 200 | "source": [ 201 | "action1.run()" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 79, 207 | "id": "4d244dc4-01c9-4faf-9381-7f35fbe467c7", 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "charshape = app.actions.CharShape()" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": 80, 217 | "id": "c0f619b6-2fe0-4217-b1e5-5c1e029f4726", 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [ 221 | "charshape.pset.Height = app.api.PointToHwpUnit(20)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 81, 227 | "id": "78d0cd48-2a41-480e-b284-21db07b86343", 228 | "metadata": {}, 229 | "outputs": [ 230 | { 231 | "data": { 232 | "text/plain": [ 233 | "True" 234 | ] 235 | }, 236 | "execution_count": 81, 237 | "metadata": {}, 238 | "output_type": "execute_result" 239 | } 240 | ], 241 | "source": [ 242 | "charshape.run()" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 82, 248 | "id": "7f637594-0af7-4065-9d8e-0b84e67bd356", 249 | "metadata": {}, 250 | "outputs": [ 251 | { 252 | "data": { 253 | "text/plain": [ 254 | "True" 255 | ] 256 | }, 257 | "execution_count": 82, 258 | "metadata": {}, 259 | "output_type": "execute_result" 260 | } 261 | ], 262 | "source": [ 263 | "action2.pset.Text = \"크게 입력하기\"\n", 264 | "action2.run()" 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "id": "264128ea-247e-4437-9a01-02fc5c682c54", 270 | "metadata": {}, 271 | "source": [ 272 | "## 입력하기" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "id": "3cefd551-6eb9-4c3c-bdac-64167128b7db", 278 | "metadata": {}, 279 | "source": [ 280 | "입력은 가장 자주 사용하는 것이기 때문에 다음과 같이 `App`의 메소드로 만들었습니다." 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 83, 286 | "id": "d770711b-1f4c-420d-90f9-c078469975df", 287 | "metadata": {}, 288 | "outputs": [], 289 | "source": [ 290 | "app.insert_text(\"더 크게 입력하기\", height=30)" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 84, 296 | "id": "7151e810-756c-484c-a3a6-c08bd79e034c", 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "action = app.actions.Select()" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "id": "9c4cd9de-760a-451f-8d6f-b4f002b13ae4", 306 | "metadata": {}, 307 | "source": [ 308 | "## 이동하기\n", 309 | "\n", 310 | "크게 두가지 방법이 있습니다. \n", 311 | "\n", 312 | "액션을 사용하는 방법과 단어를 찾아가는 방법이 있습니다." 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 85, 318 | "id": "790d7e99-6cf2-491c-865e-c4f281528447", 319 | "metadata": {}, 320 | "outputs": [ 321 | { 322 | "data": { 323 | "text/plain": [ 324 | "True" 325 | ] 326 | }, 327 | "execution_count": 85, 328 | "metadata": {}, 329 | "output_type": "execute_result" 330 | } 331 | ], 332 | "source": [ 333 | "app.actions.MoveColumnBegin().run()" 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "id": "acd1ca4a-e54f-4c63-b514-28f9779c36f1", 339 | "metadata": {}, 340 | "source": [ 341 | "actions를 아래와 같이 하여 찾는 경로를 깗게 할 수도 있습니다." 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 86, 347 | "id": "b55198fe-c853-45b5-98b3-327c9400b600", 348 | "metadata": {}, 349 | "outputs": [ 350 | { 351 | "data": { 352 | "text/plain": [ 353 | "True" 354 | ] 355 | }, 356 | "execution_count": 86, 357 | "metadata": {}, 358 | "output_type": "execute_result" 359 | } 360 | ], 361 | "source": [ 362 | "actions = app.actions\n", 363 | "actions.MoveColumnBegin().run()" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 87, 369 | "id": "1ed03ea8-be34-42ab-9ce3-deed5fc4526f", 370 | "metadata": {}, 371 | "outputs": [ 372 | { 373 | "data": { 374 | "text/plain": [ 375 | "False" 376 | ] 377 | }, 378 | "execution_count": 87, 379 | "metadata": {}, 380 | "output_type": "execute_result" 381 | } 382 | ], 383 | "source": [ 384 | "app.find_text(\"해해\")" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "id": "97a580d6-cf70-4803-87af-44f12fab2e3e", 390 | "metadata": {}, 391 | "source": [ 392 | "## 텍스트 선택하기\n", 393 | "\n", 394 | "문서에서 드래그한 것과 같이 영역을 선택할 수 있습니다." 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": 88, 400 | "id": "5395dfcd-7219-443f-9469-b4a08fa5092b", 401 | "metadata": {}, 402 | "outputs": [ 403 | { 404 | "data": { 405 | "text/plain": [ 406 | "(True, True)" 407 | ] 408 | }, 409 | "execution_count": 88, 410 | "metadata": {}, 411 | "output_type": "execute_result" 412 | } 413 | ], 414 | "source": [ 415 | "app.select_text(\"Para\")" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "id": "d719942e-7b79-4678-8549-5a3798ac54af", 421 | "metadata": {}, 422 | "source": [ 423 | "## 서식 넣기\n", 424 | "\n", 425 | "선택한 영역에 글자 서식을 넣을 수 있습니다." 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 89, 431 | "id": "7f0e8f66-4b74-4f75-bff8-3200a2f08ec7", 432 | "metadata": {}, 433 | "outputs": [ 434 | { 435 | "data": { 436 | "text/plain": [ 437 | "True" 438 | ] 439 | }, 440 | "execution_count": 89, 441 | "metadata": {}, 442 | "output_type": "execute_result" 443 | } 444 | ], 445 | "source": [ 446 | "app.set_charshape(fontname=\"바탕체\", height=25, bold=True)" 447 | ] 448 | }, 449 | { 450 | "cell_type": "markdown", 451 | "id": "6fd0ae3d", 452 | "metadata": {}, 453 | "source": [ 454 | "문단 서식 또한 가능합니다." 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": 90, 460 | "id": "b40483d3", 461 | "metadata": {}, 462 | "outputs": [ 463 | { 464 | "data": { 465 | "text/plain": [ 466 | "True" 467 | ] 468 | }, 469 | "execution_count": 90, 470 | "metadata": {}, 471 | "output_type": "execute_result" 472 | } 473 | ], 474 | "source": [ 475 | "app.set_parashape(left_margin=50)" 476 | ] 477 | }, 478 | { 479 | "cell_type": "markdown", 480 | "id": "707457d9-c86b-43b4-bb0d-fe9456e0486f", 481 | "metadata": {}, 482 | "source": [ 483 | "## 텍스트 가져오기" 484 | ] 485 | }, 486 | { 487 | "cell_type": "markdown", 488 | "id": "3b63bb75", 489 | "metadata": {}, 490 | "source": [ 491 | "현재 위치의 문장이나 텍스트를 가져 올 수 있습니다.\n", 492 | "기본은 현재 문장의 시작에서 문장의 끝을 선택합니다.\n" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": 91, 498 | "id": "c0ea4e59-f4be-4bd3-8451-e97491f7f6fa", 499 | "metadata": {}, 500 | "outputs": [ 501 | { 502 | "data": { 503 | "text/plain": [ 504 | "'입력하기크게 입력하기더 크게 입력하기\\r\\n'" 505 | ] 506 | }, 507 | "execution_count": 91, 508 | "metadata": {}, 509 | "output_type": "execute_result" 510 | } 511 | ], 512 | "source": [ 513 | "app.get_text()" 514 | ] 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "id": "23add90d", 519 | "metadata": {}, 520 | "source": [ 521 | "선택 영역만 가져올 수도 있습니다." 522 | ] 523 | }, 524 | { 525 | "cell_type": "code", 526 | "execution_count": 92, 527 | "id": "085dc66d-760c-4e22-90d7-79f974e4aa61", 528 | "metadata": {}, 529 | "outputs": [ 530 | { 531 | "data": { 532 | "text/plain": [ 533 | "'입력하기크게 입력하기더 크게 입력하기\\n'" 534 | ] 535 | }, 536 | "execution_count": 92, 537 | "metadata": {}, 538 | "output_type": "execute_result" 539 | } 540 | ], 541 | "source": [ 542 | "app.get_selected_text()" 543 | ] 544 | }, 545 | { 546 | "cell_type": "code", 547 | "execution_count": null, 548 | "id": "5511b9fe-cf31-484d-ad8b-f6176586f95a", 549 | "metadata": {}, 550 | "outputs": [], 551 | "source": [] 552 | } 553 | ], 554 | "metadata": { 555 | "kernelspec": { 556 | "display_name": "python3", 557 | "language": "python", 558 | "name": "python3" 559 | }, 560 | "language_info": { 561 | "codemirror_mode": { 562 | "name": "ipython", 563 | "version": 3 564 | }, 565 | "file_extension": ".py", 566 | "mimetype": "text/x-python", 567 | "name": "python", 568 | "nbconvert_exporter": "python", 569 | "pygments_lexer": "ipython3", 570 | "version": "3.10.1" 571 | } 572 | }, 573 | "nbformat": 4, 574 | "nbformat_minor": 5 575 | } 576 | -------------------------------------------------------------------------------- /nbs/01_tutorials/02_find_and_replace.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "---\n", 8 | "title: \"찾아바꾸기 기능 활용\"\n", 9 | "description: 찾아바꾸기를 활용한 파이썬 자동화 사례\n", 10 | "output-file: find_replace.html\n", 11 | "---" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## 문제설정\n", 19 | "\n", 20 | "문서 작업을 하면서 같은 의미지만 다르게 작성하여 형식을 통일하기 위해 문서를 처음부터 검토해야 하는 경우가 있습니다.\n", 21 | "예를 들어 \"2022년\"이라고 쓰는 경우도 있고 \"'22년\"으로 적는 경우도 있습니다. 이를 모두 2022년으로 작성 방식을 통일하고자 한다면 찾아바꾸기를 통해 쉽게 달성할 수 있습니다.\n", 22 | "\n", 23 | "만약 이런 바꿔야 하는 단어가 수십개가 된다면 어떻게 될까요?\n", 24 | "붙여써야 하는 경우, 자주 틀리는 오탈자, 영문명으로 바로 작성하거나 이니셜로만 작성하는 등, 수십개의 케이스를 모두 적용하는 것은 상당히 귀찮고 오류가 발생하기 쉬운 일입니다.\n", 25 | "\n", 26 | "이런 문제를 `hwpapi`를 사용해 해결해 보고자 합니다.\n", 27 | "\n", 28 | "[국토부 보도자료](http://www.molit.go.kr/USR/NEWS/m_71/dtl.jsp?id=95086857)를 보면 임대차 시장 안정 및 3분기 부동산 정상화 방안이라는 문서를 볼 수 있습니다.\n", 29 | "\n", 30 | "여기서 보면 '주거 안정'이라고 띄어 쓴 경우와 '주거안정'이라고 붙여쓴 경우가 있습니다.\n", 31 | "![](img/주거안정.png)\n", 32 | "\n", 33 | "유사하게 '분양가 상한제'와 같이 띄어 쓴 경우와 '분양가상한제'라고 붙여 쓴 경우가 있죠.\n", 34 | "![](img/분양가상한제1.png)\n", 35 | "![](img/분양가상한제2.png)\n", 36 | "\n", 37 | "또한 '시범사업지'와 '시범 사업지'와 같이 경우에 따라 붙이거나 띄는 경우는 한국어 특성상 자주 발생합니다. \n", 38 | "![](img/시범사업지.png)\n", 39 | "\n", 40 | "이런 항목을 모두 붙여 쓰는 스크립트를 짜보도록 하겠습니다.\n", 41 | "\n", 42 | "해야 할 일은 \n", 43 | "\n", 44 | "1. 문서 불러오기\n", 45 | "2. 기존과 변경할 것 목록 만들기\n", 46 | "3. 찾아 바꾸기\n", 47 | "\n", 48 | "이렇게 3단계로 구성됩니다." 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "### 문서 불러오기\n", 56 | "\n", 57 | "우선 패키지를 불러오고 문서를 불러 옵니다.\n", 58 | "저는 `hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp` 파일을 읽어 오겠습니다." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 1, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "from hwpapi.core import App\n", 68 | "\n", 69 | "app = App()\n", 70 | "app.open(\"hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp\")" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "### 기존 단어와 변경할 단어 목록 만들기\n", 78 | "\n", 79 | "아래와 같이 기존 단어와 변경할 단어를 만들어 둡니다.\n", 80 | "여기서는 단순히 `list`를 사용했지만, `pandas` 등을 사용하면 엑셀 파일에서 관리할 수 있습니다." 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 2, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "words = [(\"분양가 상한제\", \"분양가상한제\"), (\"주거안정\", \"주거 안정\"), (\"시범사업지\", \"시범 사업지\")]" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "### 찾아바꾸기\n", 97 | "\n", 98 | "이렇게 까지 되면 나머지는 간단합니다. `words`를 순환 하면서 반복해 주기만 하면 됩니다.\n", 99 | "모두 찾아바꾸기를 하면 어디를 바꾸었는지 확인하기 어렵기 때문에 바꾼 단어는 붉은 색으로 처리해서 쉽게 눈으로 확인해 볼 수 있게 하겠습니다.\n", 100 | "그러기 위해서 `CharShape`이라고 하는 `dataclass`를 불러오겠습니다." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 3, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "from hwpapi.dataclasses import CharShape\n", 110 | "\n", 111 | "for old, new in words:\n", 112 | " app.replace_all(old, new, new_charshape=CharShape(text_color=\"#FF0000\"))\n", 113 | " " 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "코드를 실행하고 나면 아래와 같이 바뀐 단어는 붉은색으로 표시되게 됩니다.\n", 121 | "![](img/주거안정_변경.png)\n", 122 | "![](img/시범사업지_변경.png)\n", 123 | "\n", 124 | "이렇게 변경된 사항을 눈으로 확인하고 최종적으로 단축키 등으로 정리하면 문서 전체적으로 맞춰야 하는 단어나 자주 틀리는 오탈자를 쉽게 관리 할 수 있게 됩니다." 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 10, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 31, 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "ename": "KeyError", 141 | "evalue": "'DownOCell를 해당하는 키 중 찾을 수 없습니다. 키는 Main, CurList, TopOfFile, BottomOfFile, TopOfList, BottomOfList, StartOfPara, EndOfPara, StartOfWord, EndOfWord, NextPara, PrevPara, NextPos, PrevPos, NextPosEx, PrevPosEx, NextChar, PrevChar, NextWord, PrevWord, NextLine, PrevLine, StartOfLine, EndOfLine, ParentList, TopLevelList, RootList, CurrentCaret, LeftOfCell, RightOfCell, UpOfCell, DownOfCell, StartOfCell, EndOfCell, TopOfCell, BottomOfCell, ScrPos, ScanPos 중에 있어야 합니다.'", 142 | "output_type": "error", 143 | "traceback": [ 144 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 145 | "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", 146 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\functions.py:114\u001b[0m, in \u001b[0;36mget_value\u001b[1;34m(dict_, key)\u001b[0m\n\u001b[0;32m 113\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[1;32m--> 114\u001b[0m \u001b[39mreturn\u001b[39;00m dict_[key]\n\u001b[0;32m 115\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n", 147 | "\u001b[1;31mKeyError\u001b[0m: 'DownOCell'", 148 | "\nDuring handling of the above exception, another exception occurred:\n", 149 | "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", 150 | "Cell \u001b[1;32mIn[31], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m app\u001b[39m.\u001b[39;49mmove(\u001b[39m\"\u001b[39;49m\u001b[39mDownOCell\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n", 151 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\core.py:497\u001b[0m, in \u001b[0;36mmove\u001b[1;34m(app, key, para, pos)\u001b[0m\n\u001b[0;32m 493\u001b[0m \u001b[39m@patch\u001b[39m\n\u001b[0;32m 494\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mmove\u001b[39m(app: App, key\u001b[39m=\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mScanPos\u001b[39m\u001b[39m\"\u001b[39m, para\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m, pos\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m):\n\u001b[0;32m 495\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"키워드를 바탕으로 캐럿 위치를 이동시킵니다.\"\"\"\u001b[39;00m\n\u001b[1;32m--> 497\u001b[0m move_id \u001b[39m=\u001b[39m get_value(move_ids, key)\n\u001b[0;32m 498\u001b[0m \u001b[39mreturn\u001b[39;00m app\u001b[39m.\u001b[39mapi\u001b[39m.\u001b[39mMovePos(moveID\u001b[39m=\u001b[39mmove_id, Para\u001b[39m=\u001b[39mpara, pos\u001b[39m=\u001b[39mpos)\n", 152 | "File \u001b[1;32mc:\\users\\freed\\documents\\python_projects\\hwpapi\\hwpapi\\functions.py:116\u001b[0m, in \u001b[0;36mget_value\u001b[1;34m(dict_, key)\u001b[0m\n\u001b[0;32m 114\u001b[0m \u001b[39mreturn\u001b[39;00m dict_[key]\n\u001b[0;32m 115\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mKeyError\u001b[39;00m:\n\u001b[1;32m--> 116\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mKeyError\u001b[39;00m(\n\u001b[0;32m 117\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m{\u001b[39;00mkey\u001b[39m}\u001b[39;00m\u001b[39m를 해당하는 키 중 찾을 수 없습니다. 키는 \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39m'\u001b[39m\u001b[39m, \u001b[39m\u001b[39m'\u001b[39m\u001b[39m.\u001b[39mjoin(dict_\u001b[39m.\u001b[39mkeys())\u001b[39m}\u001b[39;00m\u001b[39m 중에 있어야 합니다.\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m 118\u001b[0m )\n", 153 | "\u001b[1;31mKeyError\u001b[0m: 'DownOCell를 해당하는 키 중 찾을 수 없습니다. 키는 Main, CurList, TopOfFile, BottomOfFile, TopOfList, BottomOfList, StartOfPara, EndOfPara, StartOfWord, EndOfWord, NextPara, PrevPara, NextPos, PrevPos, NextPosEx, PrevPosEx, NextChar, PrevChar, NextWord, PrevWord, NextLine, PrevLine, StartOfLine, EndOfLine, ParentList, TopLevelList, RootList, CurrentCaret, LeftOfCell, RightOfCell, UpOfCell, DownOfCell, StartOfCell, EndOfCell, TopOfCell, BottomOfCell, ScrPos, ScanPos 중에 있어야 합니다.'" 154 | ] 155 | } 156 | ], 157 | "source": [ 158 | "app.move(\"DownOfCell\")" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 46, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "data": { 168 | "text/plain": [ 169 | "['Application',\n", 170 | " 'ArcType',\n", 171 | " 'AutoNumType',\n", 172 | " 'BorderShape',\n", 173 | " 'BreakWordLatin',\n", 174 | " 'BrushType',\n", 175 | " 'CLSID',\n", 176 | " 'Canonical',\n", 177 | " 'CellApply',\n", 178 | " 'CellShape',\n", 179 | " 'CharShadowType',\n", 180 | " 'CharShape',\n", 181 | " 'CheckXObject',\n", 182 | " 'Clear',\n", 183 | " 'ColDefType',\n", 184 | " 'ColLayoutType',\n", 185 | " 'ConvertPUAHangulToUnicode',\n", 186 | " 'CreateAction',\n", 187 | " 'CreateField',\n", 188 | " 'CreateID',\n", 189 | " 'CreateMode',\n", 190 | " 'CreatePageImage',\n", 191 | " 'CreateSet',\n", 192 | " 'CrookedSlash',\n", 193 | " 'CurFieldState',\n", 194 | " 'CurMetatagState',\n", 195 | " 'CurSelectedCtrl',\n", 196 | " 'DSMark',\n", 197 | " 'DbfCodeType',\n", 198 | " 'DeleteCtrl',\n", 199 | " 'Delimiter',\n", 200 | " 'DrawAspect',\n", 201 | " 'DrawFillImage',\n", 202 | " 'DrawShadowType',\n", 203 | " 'EditMode',\n", 204 | " 'Encrypt',\n", 205 | " 'EndSize',\n", 206 | " 'EndStyle',\n", 207 | " 'EngineProperties',\n", 208 | " 'ExportStyle',\n", 209 | " 'FieldExist',\n", 210 | " 'FileTranslate',\n", 211 | " 'FillAreaType',\n", 212 | " 'FindCtrl',\n", 213 | " 'FindDir',\n", 214 | " 'FindPrivateInfo',\n", 215 | " 'FontType',\n", 216 | " 'GetBinDataPath',\n", 217 | " 'GetCurFieldName',\n", 218 | " 'GetCurMetatagName',\n", 219 | " 'GetFieldList',\n", 220 | " 'GetFieldText',\n", 221 | " 'GetFileInfo',\n", 222 | " 'GetFontList',\n", 223 | " 'GetHeadingString',\n", 224 | " 'GetMessageBoxMode',\n", 225 | " 'GetMetatagList',\n", 226 | " 'GetMetatagNameText',\n", 227 | " 'GetMousePos',\n", 228 | " 'GetPageText',\n", 229 | " 'GetPos',\n", 230 | " 'GetPosBySet',\n", 231 | " 'GetScriptSource',\n", 232 | " 'GetSelectedPos',\n", 233 | " 'GetSelectedPosBySet',\n", 234 | " 'GetText',\n", 235 | " 'GetTextFile',\n", 236 | " 'GetTranslateLangList',\n", 237 | " 'GetUserInfo',\n", 238 | " 'Gradation',\n", 239 | " 'GridMethod',\n", 240 | " 'GridViewLine',\n", 241 | " 'GutterMethod',\n", 242 | " 'HAction',\n", 243 | " 'HAlign',\n", 244 | " 'HParameterSet',\n", 245 | " 'Handler',\n", 246 | " 'Hash',\n", 247 | " 'HatchStyle',\n", 248 | " 'HeadCtrl',\n", 249 | " 'HeadType',\n", 250 | " 'HeightRel',\n", 251 | " 'Hiding',\n", 252 | " 'HorzRel',\n", 253 | " 'HwpLineType',\n", 254 | " 'HwpLineWidth',\n", 255 | " 'HwpOutlineStyle',\n", 256 | " 'HwpOutlineType',\n", 257 | " 'HwpUnderlineShape',\n", 258 | " 'HwpUnderlineType',\n", 259 | " 'HwpZoomType',\n", 260 | " 'ImageFormat',\n", 261 | " 'ImportStyle',\n", 262 | " 'InitHParameterSet',\n", 263 | " 'InitScan',\n", 264 | " 'Insert',\n", 265 | " 'InsertBackgroundPicture',\n", 266 | " 'InsertCtrl',\n", 267 | " 'InsertPicture',\n", 268 | " 'IsActionEnable',\n", 269 | " 'IsCommandLock',\n", 270 | " 'IsEmpty',\n", 271 | " 'IsModified',\n", 272 | " 'IsPrivateInfoProtected',\n", 273 | " 'IsTrackChange',\n", 274 | " 'IsTrackChangePassword',\n", 275 | " 'KeyIndicator',\n", 276 | " 'LastCtrl',\n", 277 | " 'LineSpacingMethod',\n", 278 | " 'LineWrapType',\n", 279 | " 'LockCommand',\n", 280 | " 'LunarToSolar',\n", 281 | " 'LunarToSolarBySet',\n", 282 | " 'MacroState',\n", 283 | " 'MailType',\n", 284 | " 'MetatagExist',\n", 285 | " 'MiliToHwpUnit',\n", 286 | " 'ModifyFieldProperties',\n", 287 | " 'ModifyMetatagProperties',\n", 288 | " 'MovePos',\n", 289 | " 'MoveToField',\n", 290 | " 'MoveToMetatag',\n", 291 | " 'NumberFormat',\n", 292 | " 'Numbering',\n", 293 | " 'Open',\n", 294 | " 'PageCount',\n", 295 | " 'PageNumPosition',\n", 296 | " 'PageType',\n", 297 | " 'ParaHeadAlign',\n", 298 | " 'ParaShape',\n", 299 | " 'ParentCtrl',\n", 300 | " 'Path',\n", 301 | " 'PicEffect',\n", 302 | " 'PlacementType',\n", 303 | " 'PointToHwpUnit',\n", 304 | " 'PresentEffect',\n", 305 | " 'PrintDevice',\n", 306 | " 'PrintPaper',\n", 307 | " 'PrintRange',\n", 308 | " 'PrintType',\n", 309 | " 'ProtectPrivateInfo',\n", 310 | " 'PutFieldText',\n", 311 | " 'PutMetatagNameText',\n", 312 | " 'Quit',\n", 313 | " 'RGBColor',\n", 314 | " 'RegisterModule',\n", 315 | " 'RegisterPrivateInfoPattern',\n", 316 | " 'ReleaseAction',\n", 317 | " 'ReleaseScan',\n", 318 | " 'RenameField',\n", 319 | " 'RenameMetatag',\n", 320 | " 'ReplaceAction',\n", 321 | " 'ReplaceFont',\n", 322 | " 'Revision',\n", 323 | " 'Run',\n", 324 | " 'RunScriptMacro',\n", 325 | " 'Save',\n", 326 | " 'SaveAs',\n", 327 | " 'ScanFont',\n", 328 | " 'SelectText',\n", 329 | " 'SelectionMode',\n", 330 | " 'SetBarCodeImage',\n", 331 | " 'SetCurFieldName',\n", 332 | " 'SetCurMetatagName',\n", 333 | " 'SetDRMAuthority',\n", 334 | " 'SetFieldViewOption',\n", 335 | " 'SetMessageBoxMode',\n", 336 | " 'SetPos',\n", 337 | " 'SetPosBySet',\n", 338 | " 'SetPrivateInfoPassword',\n", 339 | " 'SetTextFile',\n", 340 | " 'SetTitleName',\n", 341 | " 'SetUserInfo',\n", 342 | " 'SideType',\n", 343 | " 'Signature',\n", 344 | " 'Slash',\n", 345 | " 'SolarToLunar',\n", 346 | " 'SolarToLunarBySet',\n", 347 | " 'SortDelimiter',\n", 348 | " 'StrikeOut',\n", 349 | " 'StyleType',\n", 350 | " 'SubtPos',\n", 351 | " 'TableBreak',\n", 352 | " 'TableFormat',\n", 353 | " 'TableSwapType',\n", 354 | " 'TableTarget',\n", 355 | " 'TextAlign',\n", 356 | " 'TextArtAlign',\n", 357 | " 'TextDir',\n", 358 | " 'TextFlowType',\n", 359 | " 'TextWrapType',\n", 360 | " 'UnSelectCtrl',\n", 361 | " 'VAlign',\n", 362 | " 'Version',\n", 363 | " 'VertRel',\n", 364 | " 'ViewFlag',\n", 365 | " 'ViewProperties',\n", 366 | " 'WatermarkBrush',\n", 367 | " 'WidthRel',\n", 368 | " 'XHwpDocuments',\n", 369 | " 'XHwpMessageBox',\n", 370 | " 'XHwpODBC',\n", 371 | " 'XHwpWindows',\n", 372 | " '_ApplyTypes_',\n", 373 | " '__class__',\n", 374 | " '__delattr__',\n", 375 | " '__dict__',\n", 376 | " '__dir__',\n", 377 | " '__doc__',\n", 378 | " '__eq__',\n", 379 | " '__format__',\n", 380 | " '__ge__',\n", 381 | " '__getattr__',\n", 382 | " '__getattribute__',\n", 383 | " '__gt__',\n", 384 | " '__hash__',\n", 385 | " '__init__',\n", 386 | " '__init_subclass__',\n", 387 | " '__iter__',\n", 388 | " '__le__',\n", 389 | " '__lt__',\n", 390 | " '__module__',\n", 391 | " '__ne__',\n", 392 | " '__new__',\n", 393 | " '__reduce__',\n", 394 | " '__reduce_ex__',\n", 395 | " '__repr__',\n", 396 | " '__setattr__',\n", 397 | " '__sizeof__',\n", 398 | " '__str__',\n", 399 | " '__subclasshook__',\n", 400 | " '__weakref__',\n", 401 | " '_get_good_object_',\n", 402 | " '_get_good_single_object_',\n", 403 | " '_oleobj_',\n", 404 | " '_prop_map_get_',\n", 405 | " '_prop_map_put_',\n", 406 | " 'coclass_clsid']" 407 | ] 408 | }, 409 | "execution_count": 46, 410 | "metadata": {}, 411 | "output_type": "execute_result" 412 | } 413 | ], 414 | "source": [ 415 | "dir(app.api)" 416 | ] 417 | }, 418 | { 419 | "cell_type": "code", 420 | "execution_count": 62, 421 | "metadata": {}, 422 | "outputs": [ 423 | { 424 | "data": { 425 | "text/plain": [ 426 | "(True, 1, 1, 1, 1, 3, 11, 0, '')" 427 | ] 428 | }, 429 | "execution_count": 62, 430 | "metadata": {}, 431 | "output_type": "execute_result" 432 | } 433 | ], 434 | "source": [ 435 | "app.api.KeyIndicator()" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 63, 441 | "metadata": {}, 442 | "outputs": [ 443 | { 444 | "data": { 445 | "text/plain": [ 446 | "(True, 1, 1, 1, 1, 1, 3, 0, '(C7): 문자 입력')" 447 | ] 448 | }, 449 | "execution_count": 63, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "app.api.KeyIndicator()" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 69, 461 | "metadata": {}, 462 | "outputs": [ 463 | { 464 | "data": { 465 | "text/plain": [ 466 | "['CLSID',\n", 467 | " 'CtrlCh',\n", 468 | " 'CtrlID',\n", 469 | " 'GetAnchorPos',\n", 470 | " 'HasList',\n", 471 | " 'Next',\n", 472 | " 'Prev',\n", 473 | " 'Properties',\n", 474 | " 'UserDesc',\n", 475 | " '_ApplyTypes_',\n", 476 | " '__class__',\n", 477 | " '__delattr__',\n", 478 | " '__dict__',\n", 479 | " '__dir__',\n", 480 | " '__doc__',\n", 481 | " '__eq__',\n", 482 | " '__format__',\n", 483 | " '__ge__',\n", 484 | " '__getattr__',\n", 485 | " '__getattribute__',\n", 486 | " '__gt__',\n", 487 | " '__hash__',\n", 488 | " '__init__',\n", 489 | " '__init_subclass__',\n", 490 | " '__iter__',\n", 491 | " '__le__',\n", 492 | " '__lt__',\n", 493 | " '__module__',\n", 494 | " '__ne__',\n", 495 | " '__new__',\n", 496 | " '__reduce__',\n", 497 | " '__reduce_ex__',\n", 498 | " '__repr__',\n", 499 | " '__setattr__',\n", 500 | " '__sizeof__',\n", 501 | " '__str__',\n", 502 | " '__subclasshook__',\n", 503 | " '__weakref__',\n", 504 | " '_get_good_object_',\n", 505 | " '_get_good_single_object_',\n", 506 | " '_oleobj_',\n", 507 | " '_prop_map_get_',\n", 508 | " '_prop_map_put_',\n", 509 | " 'coclass_clsid']" 510 | ] 511 | }, 512 | "execution_count": 69, 513 | "metadata": {}, 514 | "output_type": "execute_result" 515 | } 516 | ], 517 | "source": [ 518 | "dir(app.api.ParentCtrl)" 519 | ] 520 | } 521 | ], 522 | "metadata": { 523 | "kernelspec": { 524 | "display_name": "Python 3", 525 | "language": "python", 526 | "name": "python3" 527 | }, 528 | "language_info": { 529 | "codemirror_mode": { 530 | "name": "ipython", 531 | "version": 3 532 | }, 533 | "file_extension": ".py", 534 | "mimetype": "text/x-python", 535 | "name": "python", 536 | "nbconvert_exporter": "python", 537 | "pygments_lexer": "ipython3", 538 | "version": "3.10.1" 539 | }, 540 | "orig_nbformat": 4 541 | }, 542 | "nbformat": 4, 543 | "nbformat_minor": 2 544 | } 545 | -------------------------------------------------------------------------------- /nbs/01_tutorials/hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/hwps/220621(안건_1,2)임대차_시장_안정_및_3분기_부동산_정상화_방안.hwp -------------------------------------------------------------------------------- /nbs/01_tutorials/img/분양가상한제1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/분양가상한제1.png -------------------------------------------------------------------------------- /nbs/01_tutorials/img/분양가상한제2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/분양가상한제2.png -------------------------------------------------------------------------------- /nbs/01_tutorials/img/시범사업지.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/시범사업지.png -------------------------------------------------------------------------------- /nbs/01_tutorials/img/시범사업지_변경.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/시범사업지_변경.png -------------------------------------------------------------------------------- /nbs/01_tutorials/img/주거안정.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/주거안정.png -------------------------------------------------------------------------------- /nbs/01_tutorials/img/주거안정_변경.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/01_tutorials/img/주거안정_변경.png -------------------------------------------------------------------------------- /nbs/02_api/02_functions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "16a0d95a-c670-47b5-b4cc-0fc95afdde96", 7 | "metadata": {}, 8 | "source": [ 9 | "---\n", 10 | "description: action list\n", 11 | "output-file: functions.html\n", 12 | "title: functions\n", 13 | "\n", 14 | "---" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "id": "1441e8d7-6d44-4ac6-909d-5afd60eb41dc", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "%load_ext autoreload\n", 25 | "%autoreload 2" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 2, 31 | "id": "18cbd539-7d2c-464b-9de1-7ad4ce07fa67", 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "#| default_exp functions" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 3, 41 | "id": "2ad3361d-2420-4fb6-ab7b-bb73f322e64c", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "#| export\n", 46 | "\n", 47 | "import importlib.resources\n", 48 | "import os\n", 49 | "import shutil\n", 50 | "import winreg\n", 51 | "from pathlib import Path\n", 52 | "import re\n", 53 | "import win32com.client as win32 \n", 54 | "import pythoncom \n", 55 | "from win32com.client import Dispatch\n", 56 | "from win32com import client\n", 57 | "\n", 58 | "from hwpapi.constants import char_fields, para_fields" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 4, 64 | "id": "59da99a1-f3de-4c23-80f8-f04830ab36b6", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "#| export\n", 69 | "\n", 70 | "def get_font_name(text):\n", 71 | " m = re.search(\"(^.+?)\\s[A-Z0-9]+\\.HFT\", text)\n", 72 | " return m.group(1) if m else None" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 5, 78 | "id": "eda59f13-c968-4c35-9449-07a187cfc746", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "#| export\n", 83 | "\n", 84 | "\n", 85 | "def dispatch(app_name):\n", 86 | " \"\"\"캐시가 충돌하는 문제를 해결하기 위해 실행합니다. 에러가 발생할 경우 기존 캐시를 삭제하고 다시 불러옵니다.\"\"\"\n", 87 | " try:\n", 88 | " from win32com import client\n", 89 | "\n", 90 | " app = client.gencache.EnsureDispatch(app_name)\n", 91 | " except AttributeError:\n", 92 | " # Corner case dependencies.\n", 93 | " import os\n", 94 | " import re\n", 95 | " import shutil\n", 96 | " import sys\n", 97 | "\n", 98 | " # Remove cache and try again.\n", 99 | " MODULE_LIST = [m.__name__ for m in sys.modules.values()]\n", 100 | " for module in MODULE_LIST:\n", 101 | " if re.match(r\"win32com\\.gen_py\\..+\", module):\n", 102 | " del sys.modules[module]\n", 103 | " shutil.rmtree(os.path.join(os.environ.get(\"LOCALAPPDATA\"), \"Temp\", \"gen_py\"))\n", 104 | " from win32com import client\n", 105 | "\n", 106 | " app = client.gencache.EnsureDispatch(app_name)\n", 107 | " return app" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 6, 113 | "id": "4fe74a22-8c1a-4f3e-a17c-1fa944a99c78", 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "#| export\n", 118 | "\n", 119 | "def get_hwp_objects():\n", 120 | " context = pythoncom.CreateBindCtx(0)\n", 121 | " \n", 122 | " # 현재 실행중인 프로세스를 가져옵니다. \n", 123 | " running_coms = pythoncom.GetRunningObjectTable()\n", 124 | " monikers = running_coms.EnumRunning()\n", 125 | "\n", 126 | " hwp_objects = []\n", 127 | " for moniker in monikers:\n", 128 | " name = moniker.GetDisplayName(context,moniker);\n", 129 | " # moniker의 DisplayName을 통해 한글을 가져옵니다\n", 130 | " # 한글의 경우 HwpObject.버전으로 각 버전별 실행 이름을 설정합니다. \n", 131 | " if re.match(\"!HwpObject\", name):\n", 132 | " # 120은 한글 2022의 경우입니다. \n", 133 | " # 현재 moniker를 통해 ROT에서 한글의 object를 가져옵니다. \n", 134 | " obje = running_coms.GetObject(moniker)\n", 135 | " # 가져온 object를 Dispatch를 통해 사용할수 있는 객체로 변환시킵니다. \n", 136 | " hwp_objects.append(obje.QueryInterface(pythoncom.IID_IDispatch))\n", 137 | " return hwp_objects" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": 7, 143 | "id": "6f04ba42-85e9-4aed-a182-eb9a7b045b5f", 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "#| export\n", 148 | "\n", 149 | "\n", 150 | "def get_absolute_path(path):\n", 151 | " \"\"\"파일 절대 경로를 반환합니다.\"\"\"\n", 152 | " name = Path(path)\n", 153 | " return name.absolute().as_posix()" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 8, 159 | "id": "523811a2-7c18-4e4e-b7a1-92ab0eddb642", 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "#| export\n", 164 | "\n", 165 | "\n", 166 | "def get_dll_path(package_name, dll_filename):\n", 167 | " \"\"\"패키지에서 dll 경로를 확보합니다.\"\"\"\n", 168 | " try:\n", 169 | " with importlib.resources.path(package_name, dll_filename) as dll_path:\n", 170 | " return str(dll_path)\n", 171 | " except FileNotFoundError as e:\n", 172 | " raise FileNotFoundError(\n", 173 | " f\"The DLL file '{dll_filename}' was not found in the package '{package_name}'.\"\n", 174 | " ) from e" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 9, 180 | "id": "86241da9-ff8a-4a59-8b62-8e1c3a6f6cea", 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "#| export\n", 185 | "\n", 186 | "\n", 187 | "def add_dll_to_registry(dll_path, key_path):\n", 188 | " \"\"\"레지스트리에 dll을 등록합니다.\"\"\"\n", 189 | " try:\n", 190 | " # Connect to the registry and open the specified key\n", 191 | " registry_key = winreg.OpenKey(\n", 192 | " winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE\n", 193 | " )\n", 194 | "\n", 195 | " # Set the value for the new registry entry as a string (REG_SZ)\n", 196 | " winreg.SetValueEx(\n", 197 | " registry_key, \"FilePathCheckerModule\", 0, winreg.REG_SZ, dll_path\n", 198 | " )\n", 199 | "\n", 200 | " # Close the registry key\n", 201 | " winreg.CloseKey(registry_key)\n", 202 | " print(\"DLL path added to registry as a string value successfully.\")\n", 203 | " except WindowsError as e:\n", 204 | " print(\"Error while adding DLL path to registry: \", e)" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 10, 210 | "id": "64f0f323-fefa-442c-a6f8-437c4b9252a0", 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "#| export\n", 215 | "\n", 216 | "\n", 217 | "def get_registry_value(key_path, value_name):\n", 218 | " \"\"\"레지스트리에 값이 있는지 확인해 봅니다.\"\"\"\n", 219 | " try:\n", 220 | " with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:\n", 221 | " value, value_type = winreg.QueryValueEx(key, value_name)\n", 222 | " return value\n", 223 | " except FileNotFoundError:\n", 224 | " return None" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 11, 230 | "id": "1215c8e8-a891-4ade-8743-2520157c387c", 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "#| export\n", 235 | "\n", 236 | "\n", 237 | "def check_dll(dll_path=None):\n", 238 | " \"\"\"dll 모듈을 등록합니다.\"\"\"\n", 239 | " dll_path = dll_path if dll_path else get_dll_path(\"hwpapi\", \"FilePathCheckerModuleExample.dll\")\n", 240 | " key_path = \"SOFTWARE\\\\HNC\\\\HwpAutomation\\\\Modules\"\n", 241 | " value_name = \"FilePathCheckerModule\"\n", 242 | "\n", 243 | " value = get_registry_value(key_path, value_name)\n", 244 | "\n", 245 | " if value is None:\n", 246 | " add_dll_to_registry(dll_path, key_path)\n", 247 | " return True" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 12, 253 | "id": "75620557-aef7-4c2a-9196-d9588d3ac016", 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "#| export\n", 258 | "def get_value(dict_, key):\n", 259 | " \"\"\"딕셔너리에서 키를 찾아 값을 반환합니다. 반환할 값이 없으면 키에러와 함께 가능한 키를 알려줍니다.\"\"\"\n", 260 | " if key is None:\n", 261 | " return None\n", 262 | " try:\n", 263 | " return dict_[key]\n", 264 | " except KeyError:\n", 265 | " raise KeyError(\n", 266 | " f\"{key}를 해당하는 키 중 찾을 수 없습니다. 키는 {', '.join(dict_.keys())} 중에 있어야 합니다.\"\n", 267 | " )" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 13, 273 | "id": "7f84ed1e-2b32-4e18-9886-5dcaab8ef2b3", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "#| export\n", 278 | "def get_key(dict_, value):\n", 279 | " \"\"\"딕셔너리에서 값를 찾아 키를 반환합니다. 반환할 값이 없으면 키에러와 함께 가능한 키를 알려줍니다.\"\"\"\n", 280 | " if value is None:\n", 281 | " return None\n", 282 | " try:\n", 283 | " return dict([(v, k) for k, v in dict_.items()])[value]\n", 284 | " except KeyError:\n", 285 | " raise KeyError(\n", 286 | " f\"{value}를 해당하는 키 중 찾을 수 없습니다. 키는 {', '.join(dict_.values())} 중에 있어야 합니다.\"\n", 287 | " )" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": 14, 293 | "id": "3196074d-d81d-4bad-a73d-f6765bedfaec", 294 | "metadata": {}, 295 | "outputs": [], 296 | "source": [ 297 | "#| export \n", 298 | "\n", 299 | "def convert2int(_dict, value):\n", 300 | " if value is None:\n", 301 | " return value\n", 302 | " if isinstance(value, str):\n", 303 | " return get_value(_dict, value)\n", 304 | " if isinstance(value, int):\n", 305 | " return value\n", 306 | " if isinstance(value, float):\n", 307 | " return int(value)" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 15, 313 | "id": "094e8ea5-6fbf-4266-8875-7b66bb7535d0", 314 | "metadata": {}, 315 | "outputs": [], 316 | "source": [ 317 | "#| export \n", 318 | "\n", 319 | "def set_pset(p, value_dict:dict):\n", 320 | " for field in dir(p):\n", 321 | " value = value_dict.get(field, None)\n", 322 | " if value is None:\n", 323 | " continue\n", 324 | " setattr(p, field, value)\n", 325 | "\n", 326 | " return p" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 16, 332 | "id": "64dbbb52-4b23-4d17-b231-0beff1f1a37b", 333 | "metadata": {}, 334 | "outputs": [], 335 | "source": [ 336 | "#| export\n", 337 | "\n", 338 | "def get_charshape_pset(p):\n", 339 | " return {field: getattr(p, field) for field in char_fields}" 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": 17, 345 | "id": "e4c347ca-bc90-4cc1-b4ac-c1f36eb6546e", 346 | "metadata": {}, 347 | "outputs": [], 348 | "source": [ 349 | "#| export\n", 350 | "\n", 351 | "def set_charshape_pset(\n", 352 | " charshape_pset, value_dict:dict\n", 353 | "):\n", 354 | " \"\"\"\n", 355 | " CharShape값을 입력하기 위한 함수입니다.\n", 356 | " char_fields에 정의된 키 값을 기반으로 파라미터를 세팅합니다.\n", 357 | " \"\"\"\n", 358 | " \n", 359 | " for field in char_fields:\n", 360 | " value = value_dict.get(field, None)\n", 361 | " if not value:\n", 362 | " continue\n", 363 | " setattr(charshape_pset, field, value)\n", 364 | "\n", 365 | " return charshape_pset" 366 | ] 367 | }, 368 | { 369 | "cell_type": "code", 370 | "execution_count": 18, 371 | "id": "a1c9b3f5-2f5f-4e73-98eb-df28d3ed5d1e", 372 | "metadata": {}, 373 | "outputs": [], 374 | "source": [ 375 | "#| export\n", 376 | "def get_parashape_pset(p):\n", 377 | "\n", 378 | " return {field: getattr(p, field) for field in para_fields}" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": 19, 384 | "id": "6cfb628b-0c02-48cb-b245-b4782ad532c1", 385 | "metadata": {}, 386 | "outputs": [], 387 | "source": [ 388 | "#| export\n", 389 | "def set_parashape_pset(\n", 390 | " parashape_pset, value_dict:dict,\n", 391 | "):\n", 392 | " \n", 393 | " for field in para_fields:\n", 394 | " value = value_dict.get(field, None)\n", 395 | " if not value:\n", 396 | " continue\n", 397 | " setattr(parashape_pset, field, value)\n", 398 | "\n", 399 | " \n", 400 | " return parashape_pset" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 20, 406 | "id": "2bbd003f-52c6-4106-b942-b7dfe70d5d5c", 407 | "metadata": {}, 408 | "outputs": [], 409 | "source": [ 410 | "#| export\n", 411 | "\n", 412 | "\n", 413 | "def hex_to_rgb(hex_string):\n", 414 | " # Remove the \"#\" symbol if it exists\n", 415 | " if hex_string.startswith(\"#\"):\n", 416 | " hex_string = hex_string[1:]\n", 417 | "\n", 418 | " # Convert the hex string to decimal integers\n", 419 | " red = int(hex_string[0:2], 16)\n", 420 | " green = int(hex_string[2:4], 16)\n", 421 | " blue = int(hex_string[4:], 16)\n", 422 | "\n", 423 | " # Return the RGB tuple\n", 424 | " return (red, green, blue)\n", 425 | "\n", 426 | "\n", 427 | "def get_rgb_tuple(color):\n", 428 | " # check if the input is already a tuple\n", 429 | " if isinstance(color, tuple):\n", 430 | " # validate each color\n", 431 | " if len(color) > 3:\n", 432 | " raise ValueError(\n", 433 | " f\"colors should contains three compoents which represents RGB\"\n", 434 | " )\n", 435 | "\n", 436 | " for component in color:\n", 437 | " if component > 255:\n", 438 | " raise ValueError(\n", 439 | " f\"number should be smaller than 255. the value is {color}\"\n", 440 | " )\n", 441 | " return color\n", 442 | "\n", 443 | " # if the input is a string, split it into a list of colors\n", 444 | " elif isinstance(color, str):\n", 445 | " colors = {\n", 446 | " \"red\": (255, 0, 0),\n", 447 | " \"green\": (0, 255, 0),\n", 448 | " \"blue\": (0, 0, 255),\n", 449 | " \"black\": (0, 0, 0),\n", 450 | " \"white\": (255, 255, 255),\n", 451 | " }\n", 452 | "\n", 453 | " if color in colors.keys():\n", 454 | " return colors.get(color)\n", 455 | "\n", 456 | " # validate each color\n", 457 | " if not (\n", 458 | " color.startswith(\"#\")\n", 459 | " and len(color) == 7\n", 460 | " and all(c in \"0123456789abcdefABCDEF\" for c in color[1:])\n", 461 | " ):\n", 462 | " raise ValueError(f\"'{color}' is not a valid hexadecimal color.\")\n", 463 | "\n", 464 | " # convert the list to a tuple and return it\n", 465 | " return hex_to_rgb(color)\n", 466 | "\n", 467 | " else:\n", 468 | " raise TypeError(\"Input must be a string or a tuple of colors.\")" 469 | ] 470 | }, 471 | { 472 | "cell_type": "code", 473 | "execution_count": 21, 474 | "id": "ea3a8ee2-924a-4eef-b46b-cf7442408701", 475 | "metadata": {}, 476 | "outputs": [], 477 | "source": [ 478 | "#| export \n", 479 | "def convert_to_hwp_color(color):\n", 480 | " \n", 481 | " if isinstance(color, int):\n", 482 | " return color \n", 483 | " \n", 484 | " if isinstance(color, str): # if the color is a string, we assume it's a hex string\n", 485 | " #hwp use bgr order\n", 486 | " colors = {\n", 487 | " \"red\": \"0000FF\",\n", 488 | " \"green\": \"00FF00\",\n", 489 | " \"blue\": \"FF0000\",\n", 490 | " \"black\": \"000000\",\n", 491 | " \"white\": \"FFFFFF\",\n", 492 | " }\n", 493 | " \n", 494 | " if color in colors.keys():\n", 495 | " return int(colors.get(color), 16)\n", 496 | " \n", 497 | " # handle hex\n", 498 | " m = re.search(\"^#?([0-9A-Fa-f]{6})$\", color)\n", 499 | " if m:\n", 500 | " color = m.group(1)\n", 501 | " return int(f\"{color[4:6]}{color[2:4]}{color[0:2]}\", 16)\n", 502 | " \n", 503 | " elif type(color) == tuple and len(color) == 3: # if the color is a tuple, we assume it's an (R,G,B) tuple\n", 504 | " return color[2]*65536 + color[1]*256 + color[0]\n", 505 | " else:\n", 506 | " raise ValueError(f\"Unsupported color format: {color}\")" 507 | ] 508 | }, 509 | { 510 | "cell_type": "code", 511 | "execution_count": 22, 512 | "id": "258d3a35-8ad7-41c0-95a7-764d1ba2451f", 513 | "metadata": {}, 514 | "outputs": [], 515 | "source": [ 516 | "#| export\n", 517 | "def convert_hwp_color_to_hex(color:int):\n", 518 | " if not color:\n", 519 | " return color\n", 520 | " text = f\"{color:06x}\"\n", 521 | " return f\"#{text[4:6]}{text[2:4]}{text[:2]}\"\n" 522 | ] 523 | }, 524 | { 525 | "cell_type": "code", 526 | "execution_count": 23, 527 | "id": "d7ab3cde-91e3-4973-b7b9-be6fed3e8cb0", 528 | "metadata": {}, 529 | "outputs": [ 530 | { 531 | "name": "stdout", 532 | "output_type": "stream", 533 | "text": [ 534 | "16776960\n", 535 | "16776960\n" 536 | ] 537 | } 538 | ], 539 | "source": [ 540 | "print(convert_to_hwp_color(\"00FFFF\")) # Outputs: 65535\n", 541 | "print(convert_to_hwp_color((0, 255, 255))) # Outputs: 65535" 542 | ] 543 | }, 544 | { 545 | "cell_type": "code", 546 | "execution_count": 24, 547 | "id": "3b69ad8c-2b3e-44dc-9c58-45dafb2fd102", 548 | "metadata": {}, 549 | "outputs": [], 550 | "source": [ 551 | "#| export \n", 552 | "\n", 553 | "def mili2unit(value):\n", 554 | " \"\"\"\n", 555 | " 1 밀리는 283 hwpunit 입니다.\n", 556 | " \"\"\"\n", 557 | " return int(round(value*283, 0)) if value else value" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": 25, 563 | "id": "87b0a065-7cf7-4e30-a3fc-151af973c5e7", 564 | "metadata": {}, 565 | "outputs": [], 566 | "source": [ 567 | "#| export \n", 568 | "\n", 569 | "def unit2mili(value):\n", 570 | " return value/283 if value else value" 571 | ] 572 | }, 573 | { 574 | "cell_type": "code", 575 | "execution_count": 26, 576 | "id": "2f3841ea-487a-4e27-a473-57bdf4928591", 577 | "metadata": {}, 578 | "outputs": [], 579 | "source": [ 580 | "#| export \n", 581 | "def point2unit(value):\n", 582 | " \"\"\"\n", 583 | " 1point는 100 hwpunit입니다.\n", 584 | " \"\"\"\n", 585 | " return int(round(value*100, 0)) if value else value" 586 | ] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "execution_count": 27, 591 | "id": "c5dfe4ac-8ba8-4fa9-b04d-c8f4a891f256", 592 | "metadata": {}, 593 | "outputs": [], 594 | "source": [ 595 | "#| export\n", 596 | "\n", 597 | "def unit2point(value):\n", 598 | " return value / 100 if value else value" 599 | ] 600 | }, 601 | { 602 | "cell_type": "code", 603 | "execution_count": 28, 604 | "id": "6d615fed", 605 | "metadata": {}, 606 | "outputs": [], 607 | "source": [ 608 | "#| export \n", 609 | "\n", 610 | "def block_input(func):\n", 611 | " \"\"\"\n", 612 | " 함수가 실행될 동안 다른 입력을 할 수 없게 하는 기능을 가진 데코레이터입니다. \n", 613 | " \"\"\"\n", 614 | " def wrapper(app, *args, **kwargs):\n", 615 | " app.api.EditMode = 0\n", 616 | " result = func(app, *args, **kwargs)\n", 617 | " app.api.EditMode = 1\n", 618 | " return result\n", 619 | " return wrapper\n" 620 | ] 621 | }, 622 | { 623 | "cell_type": "code", 624 | "execution_count": 29, 625 | "id": "bc50f3b9-7424-4301-98a1-dc9e5a27674a", 626 | "metadata": {}, 627 | "outputs": [], 628 | "source": [ 629 | "#| hide\n", 630 | "import nbdev\n", 631 | "\n", 632 | "nbdev.nbdev_export()" 633 | ] 634 | }, 635 | { 636 | "cell_type": "code", 637 | "execution_count": null, 638 | "id": "6eac8b86", 639 | "metadata": {}, 640 | "outputs": [], 641 | "source": [] 642 | } 643 | ], 644 | "metadata": { 645 | "kernelspec": { 646 | "display_name": "python3", 647 | "language": "python", 648 | "name": "python3" 649 | }, 650 | "language_info": { 651 | "codemirror_mode": { 652 | "name": "ipython", 653 | "version": 3 654 | }, 655 | "file_extension": ".py", 656 | "mimetype": "text/x-python", 657 | "name": "python", 658 | "nbconvert_exporter": "python", 659 | "pygments_lexer": "ipython3", 660 | "version": "3.10.1" 661 | } 662 | }, 663 | "nbformat": 4, 664 | "nbformat_minor": 5 665 | } 666 | -------------------------------------------------------------------------------- /nbs/02_api/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/02_api/test.pdf -------------------------------------------------------------------------------- /nbs/02_api/test/공문양식.hwp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/02_api/test/공문양식.hwp -------------------------------------------------------------------------------- /nbs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunDamin/hwpapi/a4e28a385b8ac8bb0ff4642fef88be34fee14819/nbs/__init__.py -------------------------------------------------------------------------------- /nbs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | format: 5 | html: 6 | theme: darkly 7 | css: styles.css 8 | toc: true 9 | 10 | website: 11 | twitter-card: true 12 | open-graph: true 13 | repo-actions: [issue] 14 | navbar: 15 | background: primary 16 | search: true 17 | sidebar: 18 | style: floating 19 | 20 | metadata-files: [nbdev.yml, sidebar.yml] -------------------------------------------------------------------------------- /nbs/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "raw", 5 | "metadata": {}, 6 | "source": [ 7 | "---\n", 8 | "description: python wrapper for HWPFrame.HwpObject using win32com\n", 9 | "output-file: index.html\n", 10 | "title: hwpapi\n", 11 | "\n", 12 | "---\n", 13 | "\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "여기서 [Tutorials](https://jundamin.github.io/hwpapi/)을 볼 수 있습니다." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "vscode": { 28 | "languageId": "python" 29 | } 30 | }, 31 | "outputs": [], 32 | "source": [ 33 | "#| hide\n", 34 | "from hwpapi.core import *" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## Install" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "이 패키지는 win32com을 통해 좀더 쉽게 한글 자동화를 하기 위한 패키지 입니다.\n", 49 | "따라서, 한글 오피스가 설치된 Windows에서만 작동합니다.\n", 50 | "리눅스나 한컴 오피스가 설치된 Mac OS에서는 작동하지 않습니다." 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "다른 일반적인 패키지와 같이 아래 명령어를 입력하면 설치할 수 있습니다." 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "```sh\n", 65 | "pip install hwpapi\n", 66 | "```" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## How to use" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "기본적으로는 wi32com을 통한 한컴 오피스 자동화를 보다 쉽게 사용하기 위해 개발한 패키지 입니다. \n", 81 | "\n", 82 | "기존의 연동성을 최대한 유지하면서도 파이써닉하게 코드를 짤 수 있도록 개선하고자 하였습니다.\n", 83 | "\n", 84 | "[nbdev](https://nbdev.fast.ai/)에서 권장하는 스타일로 작성되다보니 jupyter notebook이나 jupyter lab에서는 자동완성이 잘 작동되지만, VS Code에서는 자동완성이 작동 안할 수 있습니다." 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "### 기존 코드와 연동성 비교하기\n", 92 | "\n", 93 | "[회사원 코딩](https://employeecoding.tistory.com/72)에 가보시면 아래와 같이 자동화 코드가 있습니다. \n", 94 | "\n", 95 | "```python\n", 96 | "import win32com.client as win32\n", 97 | "hwp = win32.gencache.EnsureDispatch(\"HWPFrame.HwpObject\")\n", 98 | "hwp.XHwpWindows.Item(0).Visible = True\n", 99 | "\n", 100 | "act = hwp.CreateAction(\"InsertText\")\n", 101 | "pset = act.CreateSet()\n", 102 | "pset.SetItem(\"Text\", \"Hello\\r\\nWorld!\")\n", 103 | "act.Execute(pset)\n", 104 | "```\n", 105 | "\n", 106 | "이 코드는 기본적으로 장황하다고 볼 만한 상황입니다.\n", 107 | "이 코드를 `HwpApi`를 사용하면 아래와 같이 간결하게 정리가 됨을 볼 수 있습니다." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": { 114 | "vscode": { 115 | "languageId": "python" 116 | } 117 | }, 118 | "outputs": [ 119 | { 120 | "data": { 121 | "text/plain": [ 122 | "True" 123 | ] 124 | }, 125 | "execution_count": null, 126 | "metadata": {}, 127 | "output_type": "execute_result" 128 | } 129 | ], 130 | "source": [ 131 | "from hwpapi.core import App\n", 132 | "\n", 133 | "app = App()\n", 134 | "action = app.actions.InsertText()\n", 135 | "p = action.pset\n", 136 | "p.Text = \"Hello\\r\\nWorld!\"\n", 137 | "action.run()" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "이렇게 자주 사용하는 기능은 함수로 만들었습니다." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": { 151 | "vscode": { 152 | "languageId": "python" 153 | } 154 | }, 155 | "outputs": [], 156 | "source": [ 157 | "app.insert_text(\"Hello\\r\\nWorld!\")" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "글자 모양을 바꾸는 것은 자주 있는 함수 입니다.\n", 165 | "win32com을 사용하면 아래와 같이 작성해야 합니다.\n", 166 | "\n", 167 | "```python\n", 168 | "Act = hwp.CreateAction(\"CharShape\")\n", 169 | "Set = Act.CreateSet()\n", 170 | "Act.GetDefault(Set) \n", 171 | "Set.Item(\"Italic\")\n", 172 | "Set.SetItem(\"Italic\", 1)\n", 173 | "Act.Execute(Set)\n", 174 | "```\n", 175 | "\n", 176 | "이렇게 자주 사용되는 기능은 함수로 만들어 사용할 수 있게 했습니다." 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": { 183 | "vscode": { 184 | "languageId": "python" 185 | } 186 | }, 187 | "outputs": [ 188 | { 189 | "data": { 190 | "text/plain": [ 191 | "True" 192 | ] 193 | }, 194 | "execution_count": null, 195 | "metadata": {}, 196 | "output_type": "execute_result" 197 | } 198 | ], 199 | "source": [ 200 | "app.set_charshape(italic=True)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "코드를 보시면 hwp를 세팅하는 부분이 간략해졌습니다.\n", 208 | "또한 파라미터 설정이 파이썬 객체처럼 설정할 수 있게 변경 되어 있는 것을 볼 수 있습니다.\n", 209 | "\n", 210 | "이런 식으로 파이썬에서 사용하기 쉽게 만들었습니다." 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "## 왜 HwpApi를 만들었나요?\n", 218 | "\n", 219 | "가장 큰 이유는 스스로 사용하기 위해서 입니다.\n", 220 | "직장인으로 많은 한글 문서를 편집하고 작성하곤 하는데 단순 반복업무가 너무 많다는 것이 불만이었습니다.\n", 221 | "이런 문제를 해결하는 방법으로 한글 자동화에 대한 이야기를 파이콘에서 보게 되었습니다.\n", 222 | "특히 '회사원 코딩' 님의 블로그와 영상이 많은 참조가 되었습니다.\n", 223 | "\n", 224 | "다만 그 과정에서 설명자료가 부족하기도 하고 예전에 작성했던 코드들을 자꾸 찾아보게 되면서 아래아 한글 용 파이썬 패키지가 있으면 좋겠다는 생각을 했습니다.\n", 225 | "특히 업무를 하면서 엑셀 자동화를 위해 xlwings를 사용해 보면서 파이썬으로 사용하기 쉽게 만든 라이브러리가 코딩 작업 효율을 엄청 올린다는 것을 깨닫게 되었습니다.\n", 226 | "\n", 227 | "제출 마감까지 해야 할 일들을 빠르게 하기 위해서 빠르게 한글 자동화가 된다면 좋겠다는 생각으로 만들게 되었습니다.\n", 228 | "\n", 229 | "기본적인 철학은 xlwings을 따라하고 있습니다. 기본적으로는 자주 쓰이는 항목들을 사용하기 쉽게 정리한 메소드 등으로 구현하고, 부족한 부분은 `App.api`형태로 `win32com`으로 하는 것과 동일한 작업이 가능하게 하여 한글 api의 모든 기능을 사용할 수 있도록 구현하였습니다.\n", 230 | "\n", 231 | "메소드로 만드는 것에는 아직 고민이 있습니다. chain과 같은 형태로 여러가지 콤비네이션을 사전에 세팅을 해야 하나 싶은 부분도 있고 실제로 유용하게 사용할 수 있는 여러가지 아이템 등도 있어서 어떤 부분까지 이 패키지에 구현할지는 고민하고 있습니다.\n", 232 | "\n", 233 | "다만 이런 형태의 작업을 통해서 어쩌면 hwp api wrapper가 활성화 되어서 단순 작업을 자동화 할 수 있기를 바라고 있습니다.\n" 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "python3", 240 | "language": "python", 241 | "name": "python3" 242 | } 243 | }, 244 | "nbformat": 4, 245 | "nbformat_minor": 4 246 | } 247 | -------------------------------------------------------------------------------- /nbs/nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "hwpapi" 6 | site-url: "https://JunDamin.github.io/hwpapi" 7 | description: "python wrapper for HWPFrame.HwpObject using win32com" 8 | repo-branch: main 9 | repo-url: "https://github.com/JunDamin/hwpapi" 10 | -------------------------------------------------------------------------------- /nbs/sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | sidebar: 3 | contents: 4 | - index.ipynb 5 | - section: tutorials 6 | contents: 7 | - 01_tutorials\01_app_basics.ipynb 8 | - 01_tutorials\02_find_and_replace.ipynb 9 | - section: api 10 | contents: 11 | - 02_api\00_core.ipynb 12 | - 02_api\01_actions.ipynb 13 | - 02_api\02_functions.ipynb 14 | - 02_api\03_dataclasses.ipynb 15 | - 02_api\04_constants.ipynb 16 | -------------------------------------------------------------------------------- /nbs/styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 5; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 5; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | # All sections below are required unless otherwise specified. 3 | # See https://github.com/fastai/nbdev/blob/master/settings.ini for examples. 4 | 5 | ### Python library ### 6 | repo = hwpapi 7 | lib_name = %(repo)s 8 | version = 0.0.2.2 9 | min_python = 3.7 10 | license = mit 11 | black_formatting = False 12 | 13 | ### nbdev ### 14 | doc_path = _docs 15 | lib_path = hwpapi 16 | nbs_path = nbs 17 | recursive = True 18 | tst_flags = notest 19 | put_version_in_init = True 20 | 21 | ### Docs ### 22 | branch = main 23 | custom_sidebar = False 24 | doc_host = https://%(user)s.github.io 25 | doc_baseurl = /%(repo)s 26 | git_url = https://github.com/%(user)s/%(repo)s 27 | title = %(lib_name)s 28 | 29 | ### PyPI ### 30 | audience = Developers 31 | author = JunDamin 32 | author_email = freedomgod@gmail.com 33 | copyright = 2023 onwards, %(author)s 34 | description = python wrapper for HWPFrame.HwpObject using win32com 35 | keywords = nbdev jupyter notebook python 36 | language = English 37 | status = 2 38 | user = JunDamin 39 | 40 | ### Optional ### 41 | requirements = 42 | fastcore 43 | pywin32 44 | # dev_requirements = 45 | # console_scripts = 46 | 47 | [build] 48 | encoding = utf-8 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | from configparser import ConfigParser 3 | import setuptools, shlex 4 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 5 | 6 | # note: all settings are in settings.ini; edit there, not here 7 | config = ConfigParser(delimiters=['=']) 8 | config.read('settings.ini') 9 | cfg = config['DEFAULT'] 10 | 11 | cfg_keys = 'version description keywords author author_email'.split() 12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 14 | setup_cfg = {o:cfg[o] for o in cfg_keys} 15 | 16 | licenses = { 17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 21 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 22 | } 23 | statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 24 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 25 | py_versions = '3.6 3.7 3.8 3.9 3.10'.split() 26 | 27 | requirements = shlex.split(cfg.get('requirements', '')) 28 | if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', '')) 29 | min_python = cfg['min_python'] 30 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 31 | dev_requirements = (cfg.get('dev_requirements') or '').split() 32 | 33 | setuptools.setup( 34 | name = cfg['lib_name'], 35 | license = lic[0], 36 | classifiers = [ 37 | 'Development Status :: ' + statuses[int(cfg['status'])], 38 | 'Intended Audience :: ' + cfg['audience'].title(), 39 | 'Natural Language :: ' + cfg['language'].title(), 40 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 41 | url = cfg['git_url'], 42 | packages = setuptools.find_packages(), 43 | package_data = { 44 | "hwpapi": ["*.dll"], 45 | }, 46 | include_package_data = True, 47 | install_requires = requirements, 48 | extras_require={ 'dev': dev_requirements }, 49 | dependency_links = cfg.get('dep_links','').split(), 50 | python_requires = '>=' + cfg['min_python'], 51 | long_description = open('README.md', encoding='utf-8').read(), 52 | long_description_content_type = 'text/markdown', 53 | zip_safe = False, 54 | entry_points = { 55 | 'console_scripts': cfg.get('console_scripts','').split(), 56 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 57 | }, 58 | **setup_cfg) 59 | 60 | 61 | --------------------------------------------------------------------------------