hwpwings 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: hwpwings
3
+ Version: 0.1.0
4
+ Summary: 파이썬으로 한글 문서를 제어할 수 있는 두번째 라이브러리입니다.
5
+ Home-page: https://www.youtube.com/@%ED%95%9C%ED%8C%8C%EC%97%91%EC%85%80
6
+ Author: 한파엑셀
7
+ Author-email: hpxl0518@gmail.com
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: pywin32>=302
11
+ Requires-Dist: pandas>=1.0.0
12
+ Requires-Dist: lxml>=4.0.0
File without changes
@@ -0,0 +1 @@
1
+ from .hwp import HWP
@@ -0,0 +1,1259 @@
1
+ import sys
2
+ import os
3
+ import shutil
4
+ from tkinter import Tk, filedialog
5
+ from typing import Literal, Union
6
+ import win32gui
7
+ import time
8
+ import pandas as pd
9
+ import xml.etree.ElementTree as ET
10
+ import pythoncom
11
+ import win32com.client as win32
12
+
13
+ # CircularImport 오류 출력안함
14
+ devnull = open(os.devnull, 'w')
15
+ old_stdout = sys.stdout
16
+ old_stderr = sys.stderr
17
+ sys.stdout = devnull
18
+ sys.stderr = devnull
19
+
20
+ try:
21
+ import win32com.client as win32
22
+ finally:
23
+ sys.stdout = old_stdout
24
+ sys.stderr = old_stderr
25
+ devnull.close()
26
+
27
+
28
+ # temp 폴더 삭제
29
+ try:
30
+ shutil.rmtree(os.path.join(os.environ["USERPROFILE"], "AppData/Local/Temp/gen_py"))
31
+ except FileNotFoundError as e:
32
+ pass
33
+
34
+ # Type Library 파일 재생성
35
+ win32.gencache.EnsureModule('{7D2B6F3C-1D95-4E0C-BF5A-5EE564186FBC}', 0, 1, 0)
36
+
37
+ class HWP():
38
+ def __init__(self) -> None:
39
+ try:
40
+ self.hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
41
+ self.hwp.XHwpWindows.Item(0).Visible = True
42
+ self.num = 0
43
+ try:
44
+ self.hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
45
+ except:
46
+ pass
47
+ except Exception as e:
48
+ # print(f"HWP 객체 생성 실패: {e}, 재시도합니다.")
49
+ pythoncom.CoUninitialize()
50
+ pythoncom.CoInitialize()
51
+
52
+ self.hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
53
+ self.hwp.XHwpWindows.Item(0).Visible = True
54
+ self.num = 0
55
+ try:
56
+ self.hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
57
+ except:
58
+ pass
59
+ # self.hwp = None
60
+ time.sleep(0.2)
61
+ #----------------------------------------------추가기능----------------------------------------------------------
62
+
63
+
64
+
65
+
66
+
67
+ #----------------------------------------------추가기능----------------------------------------------------------
68
+
69
+ # 기본조작
70
+
71
+ def add_doc(self:None):
72
+ '새 문서를 연다.'
73
+ return self.hwp.XHwpDocuments.Add(0) # 0은 새 창, 1은 새 탭
74
+
75
+ def add_tab(self:None):
76
+ '새 탭을 연다.'
77
+ return self.hwp.XHwpDocuments.Add(1) # 0은 새 창, 1은 새 탭
78
+
79
+ def open(self, path:str):
80
+ '기존 한글파일을 연다.'
81
+ return self.hwp.Open(path)
82
+
83
+ def close(self:None):
84
+ '''
85
+ 활성화된 한글파일을 저장하지 않고 닫는다.
86
+ 단 열려있는 문서가 1개라면, 창이 꺼지지 않는다.
87
+ '''
88
+ self.hwp.Clear(1)
89
+ return self.hwp.XHwpDocuments.Active_XHwpDocument.Close(isDirty=False)
90
+
91
+ def save(self:None, save_if_dirty=True):
92
+ """
93
+ 현재 편집중인 문서를 저장한다.
94
+ 문서의 경로가 지정되어있지 않으면 “새 이름으로 저장” 대화상자가 뜬다.
95
+ """
96
+ return self.hwp.Save(save_if_dirty=save_if_dirty)
97
+
98
+ def saveAs(self):
99
+ """
100
+ 파일을 이미지와 함께 저장할 수 있도록 하는 메서드.
101
+ 파일 저장 대화상자를 사용하여 경로와 이름을 지정합니다.
102
+ """
103
+ # Tkinter 초기화 (GUI 창 숨김)
104
+ root = Tk()
105
+ root.withdraw()
106
+
107
+ # 파일 저장 경로와 이름을 지정하는 대화 상자 열기
108
+ file_path = filedialog.asksaveasfilename(
109
+ defaultextension=".hwpx",
110
+ filetypes=[("HWPX files", "*.hwpx"), ("All files", "*.*")],
111
+ title="파일 저장 위치와 이름을 선택하세요."
112
+ )
113
+
114
+ # 파일 경로가 선택된 경우에만 실행
115
+ if file_path:
116
+ self.hwp.HAction.GetDefault("PictureSaveAsAll", self.hwp.HParameterSet.HSaveAsImage.HSet)
117
+ self.hwp.HAction.Execute("PictureSaveAsAll", self.hwp.HParameterSet.HSaveAsImage.HSet)
118
+ self.hwp.HAction.GetDefault("FileSaveAs_S", self.hwp.HParameterSet.HFileOpenSave.HSet)
119
+ self.hwp.HParameterSet.HFileOpenSave.HSet.SetItem("FileName", file_path)
120
+ self.hwp.HParameterSet.HFileOpenSave.HSet.SetItem("Format", "HWPX")
121
+ self.hwp.HAction.Execute("FileSaveAs_S", self.hwp.HParameterSet.HFileOpenSave.HSet)
122
+
123
+ def save_As(self, path, format="HWPX", arg=""):
124
+ """
125
+ 현재 편집중인 문서를 지정한 이름으로 저장한다.
126
+ format, arg의 일반적인 개념에 대해서는 Open()참조.
127
+ "Hwp" 포맷으로 파일 저장 시 arg에 지정할 수 있는 옵션은 다음과 같다.
128
+ "lock:true" - 저장한 후 해당 파일을 계속 오픈한 상태로 lock을 걸지 여부
129
+ "backup:false" - 백업 파일 생성 여부
130
+ "compress:true" - 압축 여부
131
+ "fullsave:false" - 스토리지 파일을 완전히 새로 생성하여 저장
132
+ "prvimage:2" - 미리보기 이미지 (0=off, 1=BMP, 2=GIF)
133
+ "prvtext:1" - 미리보기 텍스트 (0=off, 1=on)
134
+ "autosave:false" - 자동저장 파일로 저장할 지 여부 (TRUE: 자동저장, FALSE: 지정 파일로 저장)
135
+ "export" - 다른 이름으로 저장하지만 열린 문서는 바꾸지 않는다.(lock:false와 함께 설정되어 있을 시 동작)
136
+ 여러 개를 한꺼번에 할 경우에는 세미콜론으로 구분하여 연속적으로 사용할 수 있다.
137
+ "lock:TRUE;backup:FALSE;prvtext:1"
138
+
139
+ :param path:
140
+ 문서 파일의 전체경로
141
+
142
+ :param format:
143
+ 문서 형식. 생략하면 "HWPX"가 지정된다.
144
+
145
+ :param arg:
146
+ 세부 옵션. 의미는 format에 지정한 파일 형식에 따라 다르다. 생략하면 빈 문자열이 지정된다.
147
+
148
+ :return:
149
+ 성공하면 True, 실패하면 False
150
+ """
151
+ if path.lower()[1] != ":":
152
+ path = os.path.join(os.getcwd(), path)
153
+ return self.hwp.SaveAs(Path=path, Format=format, arg=arg)
154
+
155
+
156
+ # 인터페이스 조작하기
157
+ @property
158
+ def XHwpWindows(self:None):
159
+ return self.hwp.XHwpWindows
160
+
161
+ @property
162
+ def title(self) -> str: # self의 타입은 생략하거나 HwpController로 지정할 수 있음
163
+ if self.num == 0:
164
+ self.hwp.XHwpDocuments.Item(self.num).SetActive_XHwpDocument()
165
+ return self.get_active_window_title()
166
+ else:
167
+ self.hwp.XHwpDocuments.Item(self.num).SetActive_XHwpDocument()
168
+ return self.get_active_window_title()
169
+
170
+ def maximize_window(self:None):
171
+ """현재 창 최대화"""
172
+ win32gui.ShowWindow(
173
+ self.XHwpWindows.Active_XHwpWindow.WindowHandle, 3)
174
+
175
+ def minimize_window(self:None):
176
+ """현재 창 최소화"""
177
+ win32gui.ShowWindow(
178
+ self.XHwpWindows.Active_XHwpWindow.WindowHandle, 6)
179
+
180
+ def show_window(self:None):
181
+ """백그라운드의 한글파일을 보여줍니다"""
182
+ win32gui.ShowWindow(
183
+ self.XHwpWindows.Active_XHwpWindow.WindowHandle, 5)
184
+
185
+ def hide_window(self:None):
186
+ """한글파일을 백그라운드로 숨깁니다"""
187
+ win32gui.ShowWindow(
188
+ self.XHwpWindows.Active_XHwpWindow.WindowHandle, 0)
189
+
190
+ def quit(self:None):
191
+ """
192
+ 한/글을 종료한다.
193
+ 단, 저장되지 않은 변경사항이 있는 경우 팝업이 뜨므로
194
+ clear나 save 등의 메서드를 실행한 후에 quit을 실행해야 한다.
195
+ :return:
196
+ """
197
+ self.hwp.XHwpDocuments.Close(isDirty=False)
198
+ self.hwp.Quit()
199
+ del self.hwp
200
+
201
+ # 페이지 조작하기
202
+ @property
203
+ def page_Count(self): # 제어중인 한글 페이지수를 리턴합니다
204
+ """
205
+ 현재 문서의 총 페이지 수를 리턴한다.
206
+ :return:
207
+ """
208
+ return self.hwp.PageCount
209
+
210
+ @property
211
+ def current_page(self):
212
+ """
213
+ 현재 쪽번호를 리턴.
214
+ 1페이지에 있다면 1을 리턴한다.
215
+ 새쪽번호가 적용되어 있어도
216
+ 페이지의 인덱스를 리턴한다.
217
+ :return:
218
+ """
219
+ return self.hwp.XHwpDocuments.Active_XHwpDocument.XHwpDocumentInfo.CurrentPage + 1
220
+
221
+ @property
222
+ def page_source(self, format="HWPML2X", option=""):
223
+ '''
224
+ 현재 열려있는 한글파일의 xml을 얻어온다.
225
+ soup.select("TEXT > CHAR") 로 모든 텍스트를 추출할 수 있다.
226
+
227
+ from bs4 import BeautifulSoup
228
+
229
+ # XML 데이터를 파싱
230
+ xml = hwp.page_source
231
+ soup = BeautifulSoup(xml, 'xml')
232
+
233
+ # 전체 TEXT > CHAR 요소 추출
234
+ all_chars = soup.select("TEXT > CHAR")
235
+
236
+ # 표(TABLE) 내부의 TEXT > CHAR 요소 추출
237
+ table_chars = []
238
+ for table in soup.select("TABLE"):
239
+ table_chars.extend(table.select("TEXT > CHAR"))
240
+
241
+ # all_chars에서 table_chars에 속하지 않는 요소들만 선택
242
+ outside_table_chars = []
243
+
244
+ # all_chars 리스트를 하나씩 순회
245
+ for char in all_chars:
246
+ # 만약 char가 table_chars에 포함되지 않으면
247
+ if char not in table_chars:
248
+ # outside_table_chars 리스트에 추가
249
+ outside_table_chars.append(char)
250
+
251
+ # 결과 출력
252
+ for char in outside_table_chars:
253
+ print(char.text)
254
+ '''
255
+ return self.hwp.GetTextFile(Format=format, option=option)
256
+
257
+ def page_Copy(self:None):
258
+ """
259
+ 쪽 복사
260
+ """
261
+ return self.hwp.HAction.Run("CopyPage")
262
+
263
+ def page_Paste(self:None):
264
+ """
265
+ 쪽 붙여넣기
266
+ """
267
+ return self.hwp.HAction.Run("PastePage")
268
+
269
+ def page_Delete(self:None):
270
+ """
271
+ 쪽 지우기
272
+ """
273
+ return self.hwp.HAction.Run("DeletePage")
274
+
275
+ def goto_Startpage(self:None):
276
+ '문서의 시작으로 이동한다'
277
+ return self.hwp.HAction.Run("MoveDocBegin")
278
+
279
+ def goto_Endpage(self:None):
280
+ '문서의 끝으로 이동한다'
281
+ return self.hwp.HAction.Run("MoveDocEnd")
282
+
283
+ def goto_page(self, page_index: int | str = 1) -> tuple[int, int]: # 지정한 페이지로 이동합니다.
284
+ """
285
+ 새쪽번호와 관계없이 페이지 순서를 통해
286
+ 특정 페이지를 찾아가는 메서드.
287
+ 1이 1페이지임.
288
+ :param page_index:
289
+ :return: tuple(인쇄기준페이지, 페이지인덱스)
290
+ """
291
+ if int(page_index) > self.hwp.PageCount:
292
+ raise ValueError("입력한 페이지 인덱스가 문서 총 페이지보다 큽니다.")
293
+ elif int(page_index) < 1:
294
+ raise ValueError("1 이상의 값을 입력해야 합니다.")
295
+ self.goto_printpage(page_index)
296
+ cur_page = self.current_page
297
+ if page_index == cur_page:
298
+ pass
299
+ elif page_index < cur_page:
300
+ for _ in range(cur_page - page_index):
301
+ self.MovePageUp()
302
+ else:
303
+ for _ in range(page_index - cur_page):
304
+ self.MovePageDown()
305
+ return self.current_printpage, self.current_page
306
+
307
+ # 표(셀) 조작하기
308
+
309
+ def insert_table(self, rows, cols, treat_as_char: bool = True, width_type=0, height_type=0, header=True, height=0):
310
+ """
311
+ 표를 생성하는 메서드.
312
+ 기본적으로 rows와 cols만 지정하면 되며,
313
+ 용지여백을 제외한 구간에 맞춰 표 너비가 결정된다.
314
+ 이는 일반적인 표 생성과 동일한 수치이다.
315
+
316
+ 아래의 148mm는 종이여백 210mm에서 60mm(좌우 각 30mm)를 뺀 150mm에다가,
317
+ 표 바깥여백 각 1mm를 뺀 148mm이다. (TableProperties.Width = 41954)
318
+ 각 열의 너비는 5개 기준으로 26mm인데 이는 셀마다 안쪽여백 좌우 각각 1.8mm를 뺀 값으로,
319
+ 148 - (1.8 x 10 =) 18mm = 130mm
320
+ 그래서 셀 너비의 총 합은 130이 되어야 한다.
321
+ 아래의 라인28~32까지 셀너비의 합은 16+36+46+16+16=130
322
+ 표를 생성하는 시점에는 표 안팎의 여백을 없애거나 수정할 수 없으므로
323
+ 이는 고정된 값으로 간주해야 한다.
324
+
325
+ :return:
326
+ 표 생성 성공시 True, 실패시 False를 리턴한다.
327
+ """
328
+
329
+ pset = self.hwp.HParameterSet.HTableCreation
330
+ self.hwp.HAction.GetDefault("TableCreate", pset.HSet) # 표 생성 시작
331
+ pset.Rows = rows # 행 갯수
332
+ pset.Cols = cols # 열 갯수
333
+ pset.WidthType = width_type # 너비 지정(0:단에맞춤, 1:문단에맞춤, 2:임의값)
334
+ pset.HeightType = height_type # 높이 지정(0:자동, 1:임의값)
335
+
336
+ sec_def = self.hwp.HParameterSet.HSecDef
337
+ self.hwp.HAction.GetDefault("PageSetup", sec_def.HSet)
338
+ total_width = (
339
+ sec_def.PageDef.PaperWidth - sec_def.PageDef.LeftMargin - sec_def.PageDef.RightMargin - sec_def.PageDef.GutterLen - self.mili_to_hwp_unit(
340
+ 2))
341
+
342
+ pset.WidthValue = total_width # 표 너비(근데 영향이 없는 듯)
343
+ if height and height_type == 1: # 표높이가 정의되어 있으면
344
+ # 페이지 최대 높이 계산
345
+ total_height = (
346
+ sec_def.PageDef.PaperHeight - sec_def.PageDef.TopMargin - sec_def.PageDef.BottomMargin - sec_def.PageDef.HeaderLen - sec_def.PageDef.FooterLen - self.mili_to_hwp_unit(
347
+ 2))
348
+ pset.HeightValue = min(self.hwp.MiliToHwpUnit(height), total_height) # 표 높이
349
+ pset.CreateItemArray("RowHeight", rows) # 행 m개 생성
350
+ each_row_height = min((self.mili_to_hwp_unit(height) - self.mili_to_hwp_unit((0.5 + 0.5) * rows)) // rows,
351
+ (total_height - self.mili_to_hwp_unit((0.5 + 0.5) * rows)) // rows)
352
+ for i in range(rows):
353
+ pset.RowHeight.SetItem(i, each_row_height) # 1열
354
+ pset.TableProperties.Height = min(self.MiliToHwpUnit(height),
355
+ total_height - self.mili_to_hwp_unit((0.5 + 0.5) * rows))
356
+
357
+ pset.CreateItemArray("ColWidth", cols) # 열 n개 생성
358
+ each_col_width = total_width - self.mili_to_hwp_unit(3.6 * cols)
359
+ for i in range(cols):
360
+ pset.ColWidth.SetItem(i, each_col_width) # 1열
361
+ # pset.TableProperties.TreatAsChar = treat_as_char # 글자처럼 취급
362
+ pset.TableProperties.Width = total_width # self.hwp.MiliToHwpUnit(148) # 표 너비
363
+ self.hwp.HAction.Execute("TableCreate", pset.HSet) # 위 코드 실행
364
+
365
+ # 글자처럼 취급 여부 적용(treat_as_char)
366
+ ctrl = self.hwp.CurSelectedCtrl or self.hwp.ParentCtrl
367
+ pset = self.hwp.CreateSet("Table")
368
+ pset.SetItem("TreatAsChar", treat_as_char)
369
+ ctrl.Properties = pset
370
+
371
+ # 제목 행 여부 적용(header)
372
+ pset = self.hwp.HParameterSet.HShapeObject
373
+ self.hwp.HAction.GetDefault("TablePropertyDialog", pset.HSet)
374
+ pset.ShapeTableCell.Header = header
375
+ # try:
376
+ # self.hwp.HAction.Execute("TablePropertyDialog", pset.HSet)
377
+ # except:
378
+ # pass
379
+
380
+ def insert_cellfield(self, field : str, option=0, direction="", memo=""):
381
+ """
382
+ 현재 마우스커서(캐럿)가 깜빡이는 표의 셀에 셀필드를 생성한다.
383
+ 커서상태 or 회색선택상태(F5)에서만 필드삽입이 가능하다.
384
+ 필드가 생성되어있다면, 기존 필드에 덮어쓴다.
385
+ :return:
386
+ 성공하면 True, 실패하면 False
387
+ """
388
+ if not self.is_cell():
389
+ raise AssertionError("마우스 커서가 표 안에 있지 않습니다.")
390
+ if self.SelectionMode == 0x13:
391
+ pset = self.HParameterSet.HShapeObject
392
+ self.HAction.GetDefault("TablePropertyDialog", pset.HSet)
393
+ pset.HSet.SetItem("ShapeType", 3)
394
+ pset.HSet.SetItem("ShapeCellSize", 0)
395
+ pset.ShapeTableCell.CellCtrlData.name = field
396
+ return self.HAction.Execute("TablePropertyDialog", pset.HSet)
397
+ else:
398
+ return self.hwp.SetCurFieldName(Field=field, option=option, Direction=direction, memo=memo)
399
+
400
+ def goto_table(self, table_index:int=1, cell_address:str=''):
401
+ '''
402
+ 특정 표 안으로 이동한다.
403
+ index 기본값은 1으로 설정되어있으며,
404
+ 다른 표로 이동 시 index값을 늘려주면 된다.
405
+ '''
406
+ ctrl = self.hwp.HeadCtrl
407
+ table_dict = {}
408
+ index = 1
409
+
410
+ # 모든 표를 탐색하며 인덱스와 위치를 저장
411
+ while ctrl:
412
+ if ctrl.UserDesc == '표':
413
+ table_dict[index] = ctrl.GetAnchorPos(0)
414
+ # print(f"Table {index}: Position {ctrl.GetAnchorPos(0)}")
415
+ index += 1
416
+ ctrl = ctrl.Next
417
+
418
+ # 선택한 인덱스가 존재하는지 확인하고 이동
419
+ selected_pos = table_dict.get(table_index)
420
+ if selected_pos:
421
+ self.hwp.SetPosBySet(selected_pos)
422
+ self.hwp.FindCtrl()
423
+ self.hwp.HAction.Run('ShapeObjTableSelCell')
424
+ # cell_address가 입력된 경우 해당 셀 주소로 캐럿을 이동
425
+ if cell_address:
426
+ row, col = self.addr_to_tuple(cell_address) # cell_address를 튜플로 변환
427
+
428
+ # 행과 열을 이동
429
+ for move_col in range(col - 1): # 열 이동
430
+ self.cell_right()
431
+
432
+ for move_row in range(row - 1): # 행 이동
433
+ self.cell_down()
434
+ return True
435
+ else:
436
+ print("유효하지 않은 표 인덱스입니다.")
437
+
438
+ def delete_table(self, table_index: int = 1):
439
+ '''
440
+ 특정 표를 삭제한다.
441
+ 기본값은 첫 번째 표(index=1)이며,
442
+ 다른 표를 삭제하려면 table_index 값을 조정하면 된다.
443
+ '''
444
+ ctrl = self.hwp.HeadCtrl
445
+ table_dict = {}
446
+ index = 1
447
+
448
+ # 모든 표를 탐색하며 인덱스와 위치를 저장
449
+ while ctrl:
450
+ if ctrl.UserDesc == '표':
451
+ table_dict[index] = ctrl.GetAnchorPos(0)
452
+ # print(f"Table {index}: Position {ctrl.GetAnchorPos(0)}")
453
+ index += 1
454
+ ctrl = ctrl.Next
455
+
456
+ # 선택한 인덱스가 존재하는지 확인하고 삭제
457
+ selected_pos = table_dict.get(table_index)
458
+ if selected_pos:
459
+ self.hwp.SetPosBySet(selected_pos)
460
+ self.hwp.FindCtrl()
461
+ self.hwp.HAction.Run('Delete') # 표 삭제 명령 실행
462
+ return True
463
+ else:
464
+ print("유효하지 않은 표 인덱스입니다.")
465
+ return False
466
+
467
+ # @property
468
+ def cell_address(self):
469
+ '현재 캐럿위치의 셀 주소를 반환한다.'
470
+ return self.hwp.KeyIndicator()[-1].split(":")[0].replace('(','').replace(')','')
471
+
472
+ def cell_out(self):
473
+ '표 내부에 있을 때, 표 밖으로 나간다.'
474
+ return self.hwp.HAction.Run('Close')
475
+
476
+ def cell_left(self):
477
+ '표 내부 캐럿을 좌측으로 한칸 이동한다.'
478
+ return self.hwp.HAction.Run("TableLeftCell")
479
+
480
+ def cell_right(self):
481
+ '표 내부 캐럿을 우측으로 한칸 이동한다.'
482
+ return self.hwp.HAction.Run("TableRightCell")
483
+
484
+ def cell_rightAdd(self):
485
+ '''
486
+ 표 내부 캐럿을 우측으로 한칸 이동한다.
487
+ 만약 표의 마지막 문단이라면 표를 추가한다.
488
+ '''
489
+ return self.hwp.HAction.Run("TableRightCellAppend")
490
+
491
+ def cell_up(self):
492
+ '표 내부 캐럿을 위로 한칸 이동한다.'
493
+ return self.hwp.HAction.Run("TableUpperCell")
494
+
495
+ def cell_down(self):
496
+ '표 내부 캐럿을 아래로 한칸 이동한다.'
497
+ return self.hwp.HAction.Run("TableLowerCell")
498
+
499
+ def cell_left_end(self):
500
+ '현재 캐럿이 있는 위치를 기준으로 캐럿을 왼쪽 끝으로 이동시킨다.'
501
+ return self.hwp.HAction.Run("TableColBegin")
502
+
503
+ def cell_up_end(self):
504
+ '현재 캐럿이 있는 위치를 기준으로 캐럿을 위쪽 끝으로 이동시킨다.'
505
+ return self.hwp.HAction.Run("TableColPageUp")
506
+
507
+ def cell_right_end(self):
508
+ '현재 캐럿이 있는 위치를 기준으로 캐럿을 오른쪽 끝으로 이동시킨다.'
509
+ return self.hwp.HAction.Run("TableColEnd")
510
+
511
+ def cell_down_end(self):
512
+ '현재 캐럿이 있는 위치를 기준으로 캐럿을 아래쪽 끝으로 이동시킨다.'
513
+ return self.hwp.HAction.Run("TableColPageDown")
514
+
515
+ # 필드 조작하기
516
+
517
+ def insert_field(self, field_Name:str, Name:str): # 누름틀 필드 생성
518
+ """
519
+ 누름틀 필드를 삽입하는 메서드입니다.
520
+
521
+ Parameters:
522
+ field_Name (str): 필드의 이름
523
+ Name (str): 필드의 내용 또는 방향
524
+ """
525
+ # InsertFieldTemplate 액션의 기본값을 가져옵니다.
526
+ self.hwp.HAction.GetDefault("InsertFieldTemplate", self.hwp.HParameterSet.HInsertFieldTemplate.HSet)
527
+
528
+ # 필드 설정
529
+ self.hwp.HParameterSet.HInsertFieldTemplate.TemplateDirection = Name # 필드의 내용 또는 방향 설정
530
+ self.hwp.HParameterSet.HInsertFieldTemplate.TemplateName = field_Name # 필드의 이름 설정
531
+
532
+ # 액션 실행하여 누름틀 필드 삽입
533
+ self.hwp.HAction.Execute("InsertFieldTemplate", self.hwp.HParameterSet.HInsertFieldTemplate.HSet)
534
+
535
+ def insert_picture(self, path: str, sizeoption: int=0):
536
+ """
537
+ HWP 문서에 그림을 삽입하는 메서드입니다.
538
+
539
+ Parameters:
540
+ path (str): 삽입할 그림 파일의 경로입니다.
541
+ sizeoption (int): 그림의 크기 옵션을 지정합니다.
542
+ - 0: 이미지 원래 크기로 삽입합니다.
543
+ - 2: 셀 안에 있을 때 셀을 채웁니다 (그림 비율 무시).
544
+ - 3: 셀에 맞추되 그림 비율을 유지하여 크기를 변경합니다.
545
+
546
+ Returns:
547
+ 삽입된 그림 객체를 반환합니다.
548
+ """
549
+ return self.hwp.InsertPicture(path, sizeoption=sizeoption)
550
+
551
+ def put_field_text(self, field_Name:str, Text:str):
552
+ '지정한 필드에 넣고싶은 text를 넣습니다'
553
+ return self.hwp.PutFieldText(f"{field_Name}", f"{Text}")
554
+
555
+ def get_field_list(self:None):
556
+ '현재 한글파일에 생성된 모든 필드를 리스트로 보여줍니다.'
557
+ return self.hwp.GetFieldList(1).split('')
558
+
559
+ def get_field_text(self, field_Name:str):
560
+ '선택된 필드의 text를 추출합니다.'
561
+ return self.hwp.GetFieldText(f'{field_Name}')
562
+
563
+ def goto_field(self, field_Name:str):
564
+ '선택된 필드로 커서(캐럿)를 이동시킵니다.'
565
+ return self.hwp.MoveToField(f'{field_Name}')
566
+
567
+ def rename_field(self, oldname:str, newname:str):
568
+ """
569
+ 지정한 필드의 이름을 바꾼다.
570
+ 예를 들어 oldname에 "title{{0}}\x02title{{1}}",
571
+ newname에 "tt1\x02tt2로 지정하면 첫 번째 title은 tt1로, 두 번째 title은 tt2로 변경된다.
572
+ oldname의 필드 개수와, newname의 필드 개수는 동일해야 한다.
573
+ 존재하지 않는 필드에 대해서는 무시한다.
574
+
575
+ :param oldname:
576
+ 이름을 바꿀 필드 이름의 리스트. 형식은 PutFieldText와 동일하게 "\x02"로 구분한다.
577
+
578
+ :param newname:
579
+ 새로운 필드 이름의 리스트. oldname과 동일한 개수의 필드 이름을 "\x02"로 구분하여 지정한다.
580
+
581
+ :return: None
582
+
583
+ :example:
584
+ >>> hwp.create_field("asdf") # "asdf" 필드 생성
585
+ >>> hwp.rename_field("asdf", "zxcv") # asdf 필드명을 "zxcv"로 변경
586
+ >>> hwp.put_field_text("zxcv", "Hello world!") # zxcv 필드에 텍스트 삽입
587
+ """
588
+ return self.hwp.RenameField(oldname=oldname, newname=newname)
589
+
590
+
591
+ def delete_all_fields(self:None): # 한글 문서 내부의 모든 누름틀 필드 제거
592
+ '''
593
+ 한글문서 내부의 모든 누름틀 필드를 제거한다.
594
+ 누름틀 필드에 삽입된 텍스트는 남는다.
595
+ '''
596
+ start_pos = self.get_pos()
597
+ ctrl = self.hwp.HeadCtrl
598
+ while ctrl:
599
+ if ctrl.CtrlID == "%clk":
600
+ self.hwp.DeleteCtrl(ctrl)
601
+ ctrl = ctrl.Next
602
+ for field in self.get_field_list():
603
+ self.rename_field(field, "")
604
+ return self.set_pos(*start_pos)
605
+
606
+ # DataFrame 관련 메서드
607
+
608
+ def put_DataFrame_text(self, field, text: Union[str, list, tuple, pd.Series] = "", idx=None): # 한글의 메일머지형태로 pd.DataFrame을 필드에 적습니다. ※column의 이름이 완전히 동일해야합니다.
609
+ '한글의 메일머지형태로 pd.DataFrame을 필드에 적습니다. ※column의 이름이 완전히 동일해야합니다.'
610
+ if isinstance(field, str) and (field.endswith(".xlsx") or field.endswith(".xls")):
611
+ field = pd.read_excel(field)
612
+
613
+ if isinstance(field, dict): # dict 자료형의 경우에는 text를 생략하고
614
+ field, text = list(zip(*list(field.items())))
615
+ field_str = ""
616
+ text_str = ""
617
+ if isinstance(idx, int):
618
+ for f_i, f in enumerate(field):
619
+ field_str += f"{f}{{{{{idx}}}}}\x02"
620
+ text_str += f"{text[f_i][idx]}\x02"
621
+ else:
622
+ if isinstance(text[0], (list, tuple)):
623
+ for f_i, f in enumerate(field):
624
+ for t_i, t in enumerate(text[f_i]):
625
+ field_str += f"{f}{{{{{t_i}}}}}\x02"
626
+ text_str += f"{t}\x02"
627
+ elif isinstance(text[0], (str, int, float)):
628
+ for f_i, f in enumerate(field):
629
+ field_str += f"{f}\x02"
630
+ text_str = "\x02".join(text)
631
+
632
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
633
+ return
634
+
635
+ if isinstance(field, str) and type(text) in (list, tuple, pd.Series):
636
+ field = [f"{field}{{{{{i}}}}}" for i in range(len(text))]
637
+
638
+ if isinstance(field, pd.Series): # 필드명 리스트를 파라미터로 넣은 경우
639
+ if not text: # text 파라미터가 입력되지 않았다면
640
+ text_str = "\x02".join([field[i] for i in field.index])
641
+ field_str = "\x02".join([str(i) for i in field.index]) # \x02로 병합
642
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
643
+ return
644
+ elif type(text) in [list, tuple, pd.Series]: # 필드 텍스트를 리스트나 배열로 넣은 경우에도
645
+ text = "\x02".join([str(i) for i in text]) # \x02로 병합
646
+ else:
647
+ raise IOError("text parameter required.")
648
+
649
+ if isinstance(field, (list, tuple)):
650
+
651
+ # field와 text가 [[field0:str, list[text:str]], [field1:str, list[text:str]]] 타입인 경우
652
+ if not text and isinstance(field[0][0], (str, int, float)) and not isinstance(field[0][1], (str, int)) and len(field[0][1]) >= 1:
653
+ text_str = ""
654
+ field_str = "\x02".join(
655
+ [str(field[i][0]) + f"{{{{{j}}}}}" for j in range(len(field[0][1])) for i in range(len(field))])
656
+ for i in range(len(field[0][1])):
657
+ text_str += "\x02".join([str(field[j][1][i]) for j in range(len(field))]) + "\x02"
658
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
659
+ return
660
+
661
+ elif isinstance(field, (list, tuple, set)) and isinstance(text, (list, tuple, set)):
662
+ # field와 text가 모두 배열로 만들어져 있는 경우
663
+ field_str = "\x02".join([str(field[i]) for i in range(len(field))])
664
+ text_str = "\x02".join([str(text[i]) for i in range(len(text))])
665
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
666
+ return
667
+ else:
668
+ # field와 text가 field타입 안에 [[field0:str, text0:str], [field1:str, text1:str]] 형태로 들어간 경우
669
+ field_str = "\x02".join([str(field[i][0]) for i in range(len(field))])
670
+ text_str = "\x02".join([str(field[i][1]) for i in range(len(field))])
671
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
672
+ return
673
+
674
+ if isinstance(field, pd.DataFrame):
675
+ if isinstance(field.columns, pd.core.indexes.range.RangeIndex):
676
+ field = field.T
677
+ text_str = ""
678
+ if isinstance(idx, int):
679
+ field_str = "\x02".join([str(i) + f"{{{{{idx}}}}}" for i in field]) # \x02로 병합
680
+ text_str += "\x02".join([str(t) for t in field.iloc[idx]]) + "\x02"
681
+ else:
682
+ field_str = "\x02".join([str(i) + f"{{{{{j}}}}}" for j in range(len(field)) for i in field]) # \x02로 병합
683
+ for i in range(len(field)):
684
+ text_str += "\x02".join([str(t) for t in field.iloc[i]]) + "\x02"
685
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
686
+ return
687
+
688
+ if isinstance(text, pd.DataFrame):
689
+ if not isinstance(text.columns, pd.core.indexes.range.RangeIndex):
690
+ text = text.T
691
+ text_str = ""
692
+ if isinstance(idx, int):
693
+ field_str = "\x02".join([i + f"{{{{{idx}}}}}" for i in field.split("\x02")]) # \x02로 병합
694
+ text_str += "\x02".join([str(t) for t in text[idx]]) + "\x02"
695
+ else:
696
+ field_str = "\x02".join([str(i) + f"{{{{{j}}}}}" for i in field.split("\x02") for j in range(len(text.columns))]) # \x02로 병합
697
+ for i in range(len(text)):
698
+ text_str += "\x02".join([str(t) for t in text.iloc[i]]) + "\x02"
699
+ self.hwp.PutFieldText(Field=field_str, Text=text_str)
700
+ return
701
+
702
+ if isinstance(idx, int):
703
+ self.hwp.PutFieldText(Field=field.replace("\x02", f"{{{{{idx}}}}}\x02") + f"{{{{{idx}}}}}", Text=text)
704
+ else:
705
+ self.hwp.PutFieldText(Field=field, Text=text)
706
+
707
+ def get_DataFrame_Text(self, n="", cols=0): # Table을 DataFrame 형태로 가져옵니다.
708
+ """
709
+ (2024. 7. 26. xml파싱으로 방법 변경. 결국 기존 방법으로는 간단한 줄바꿈 이슈도 해결 못함.
710
+ startrow와 columns가 뭔가 중복되는 개념이어서, cols로 통일. 파괴적 업데이트라 죄송..)
711
+ 한/글 문서의 n번째 표를 판다스 데이터프레임으로 리턴하는 메서드.
712
+ n을 넣지 않는 경우, 캐럿이 셀에 있다면 해당 표를 df로,
713
+ 캐럿이 표 밖에 있다면 첫 번째 표를 df로 리턴한다.
714
+
715
+ ※주의 : 셀 병합이 있을 때 DataFrame을 제대로 불러오지 못한다.
716
+ :return:
717
+ pd.DataFrame
718
+ :example:
719
+ >>> from pyhwpx import Hwp
720
+ >>>
721
+ >>> hwp = Hwp()
722
+ >>> df = hwp.table_to_df() # 현재 캐럿이 들어가 있는 표 전체를 df로(1행을 df의 칼럼으로)
723
+ >>> df = hwp.table_to_df(0, cols=2) # 문서의 첫 번째 표를 df로(2번인덱스행(3행)을 칼럼명으로, 그 아래(4행부터)를 값으로)
724
+ >>>
725
+ """
726
+ if self.SelectionMode != 19:
727
+ start_pos = self.hwp.GetPos()
728
+ ctrl = self.hwp.HeadCtrl
729
+ if isinstance(n, type(ctrl)):
730
+ # 정수인덱스 대신 ctrl 객체를 넣은 경우
731
+ self.set_pos_by_set(n.GetAnchorPos(0))
732
+ self.find_ctrl()
733
+ elif n == "" and self.is_cell():
734
+ self.TableCellBlock()
735
+ self.TableColBegin()
736
+ self.TableColPageUp()
737
+ elif n == "" or isinstance(n, int):
738
+ if n == "":
739
+ n = 0
740
+ if n >= 0:
741
+ idx = 0
742
+ else:
743
+ idx = -1
744
+ ctrl = self.hwp.LastCtrl
745
+
746
+ while ctrl:
747
+ if ctrl.UserDesc == "표":
748
+ if n in (0, -1):
749
+ self.set_pos_by_set(ctrl.GetAnchorPos(0))
750
+ self.hwp.FindCtrl()
751
+ break
752
+ else:
753
+ if idx == n:
754
+ self.set_pos_by_set(ctrl.GetAnchorPos(0))
755
+ self.hwp.FindCtrl()
756
+ break
757
+ if n >= 0:
758
+ idx += 1
759
+ else:
760
+ idx -= 1
761
+ if n >= 0:
762
+ ctrl = ctrl.Next
763
+ else:
764
+ ctrl = ctrl.Prev
765
+
766
+ try:
767
+ self.hwp.SetPosBySet(ctrl.GetAnchorPos(0))
768
+ except AttributeError:
769
+ raise IndexError(f"해당 인덱스의 표가 존재하지 않습니다."
770
+ f"현재 문서에는 표가 {abs(int(idx + 0.1))}개 존재합니다.")
771
+ self.hwp.FindCtrl()
772
+ else:
773
+ selected_range = self.get_selected_range()
774
+
775
+ xml_data = self.GetTextFile("HWPML2X", option="saveblock")
776
+ root = ET.fromstring(xml_data)
777
+
778
+ data = []
779
+
780
+ for row in root.findall('.//ROW'):
781
+ row_data = []
782
+ for cell in row.findall('.//CELL'):
783
+ cell_text = ''
784
+ for text in cell.findall('.//TEXT'):
785
+ for char in text.findall('.//CHAR'):
786
+ cell_text += char.text
787
+ cell_text += "\r\n"
788
+ if cell_text.endswith("\r\n"):
789
+ cell_text = cell_text[:-2]
790
+ row_data.append(cell_text)
791
+ data.append(row_data)
792
+
793
+ if self.SelectionMode == 19:
794
+ data = self.crop_data_from_selection(data, selected_range)
795
+
796
+ if type(cols) == int:
797
+ columns = data[cols]
798
+ data = data[cols + 1:]
799
+ df = pd.DataFrame(data, columns=columns)
800
+ elif type(cols) in (list, tuple):
801
+ df = pd.DataFrame(data, columns=cols)
802
+
803
+ try:
804
+ return df
805
+ finally:
806
+ if self.SelectionMode != 19:
807
+ self.set_pos(*start_pos)
808
+
809
+ def get_all_DataFrame(self):
810
+ """
811
+ 모든 표의 데이터프레임을 얻습니다.
812
+ 여러 개일 경우 딕셔너리 {1: df1, 2: df2, ...} 형태로 리턴하고,
813
+ 하나일 경우에는 DataFrame을 그대로 리턴합니다.
814
+ """
815
+ self.data = []
816
+ # 테이블의 첫 번째 컨트롤(HeadCtrl)로 시작
817
+ ctrl = self.hwp.HeadCtrl
818
+ # self.activate()
819
+
820
+ # 테이블을 순차적으로 탐색하며 각 테이블 안으로 캐럿을 이동시키는 코드
821
+ while ctrl:
822
+ if ctrl.UserDesc == "표": # "표"인 컨트롤을 찾음
823
+ disp_val = ctrl.GetAnchorPos(0) # 테이블의 앵커 위치를 얻음
824
+
825
+ # 캐럿을 ParameterSet으로 얻은 위치로 이동
826
+ success = self.set_pos_by_set(disp_val) # 캐럿을 해당 위치로 이동시킴
827
+
828
+ if success:
829
+ self.hwp.FindCtrl() # 테이블 내의 컨트롤을 찾아 캐럿을 그 안으로 위치시킴
830
+ # pyautogui.press('enter') # 'enter' 키를 눌러 작업을 수행
831
+ self.hwp.HAction.Run("ShapeObjTableSelCell")
832
+ self.data.append(self.get_DataFrame_Text()) # 추출한 DataFrame을 리스트에 추가
833
+ # 다음 테이블로 이동
834
+ ctrl = ctrl.Next # 다음 컨트롤로 이동
835
+ else:
836
+ ctrl = ctrl.Next # 표가 아닐 경우 다음 컨트롤로 이동
837
+
838
+ # DataFrame이 하나일 경우 바로 반환
839
+ if len(self.data) == 1:
840
+ return self.data[0]
841
+
842
+ # 여러 DataFrame일 경우 번호로 딕셔너리 반환 (1, 2, 3...)
843
+ data_dict = {i + 1 : df for i, df in enumerate(self.data)}
844
+
845
+ return data_dict # 번호로 딕셔너리 반환
846
+
847
+
848
+ # 여러 한글창 조작하기
849
+
850
+ def switch_to(self, num:int):
851
+ '''
852
+ 두개 이상의 문서를 다룰 때,
853
+ 제어 한글창을 전환한다.
854
+ 0부터 시작한다.
855
+ '''
856
+ self.num = num
857
+ return self.hwp.XHwpDocuments.Item(self.num).SetActive_XHwpDocument()
858
+
859
+ # 기타 기능
860
+
861
+
862
+ def activate(self:None):
863
+ '제어중인 한글창을 활성화한다'
864
+ return self.hwp.XHwpDocuments.Item(0).SetActive_XHwpDocument()
865
+
866
+ def insert_text(self, text : str): # 진짜 글자 삽입하는 메서드임
867
+ """
868
+ 한/글 문서 내 캐럿 위치에 문자열을 삽입하는 메서드.
869
+ :return:
870
+ 삽입 성공시 True, 실패시 False를 리턴함.
871
+ :example:
872
+ >>> from pyhwpx import Hwp
873
+ >>> hwp = Hwp()
874
+ >>> hwp.insert_text('Hello world!')
875
+ >>> hwp.BreakPara()
876
+ """
877
+ param = self.hwp.HParameterSet.HInsertText
878
+ self.hwp.HAction.GetDefault("InsertText", param.HSet)
879
+ param.Text = text
880
+ return self.hwp.HAction.Execute("InsertText", param.HSet)
881
+
882
+ def to_PDF(self, path):
883
+ """
884
+ HWP 문서를 PDF로 저장하는 메서드.
885
+
886
+ :param path: 저장할 PDF 파일의 경로 (절대경로 또는 상대경로)
887
+ :return: 작업 실행 결과
888
+ """
889
+ # 상대경로를 절대경로로 변환
890
+ if path.lower()[1] != ":":
891
+ path = os.path.join(os.getcwd(), path)
892
+
893
+ # FileSaveAsPdf 기본 설정 가져오기
894
+ self.hwp.HAction.GetDefault("FileSaveAsPdf", self.hwp.HParameterSet.HFileOpenSave.HSet)
895
+
896
+ # FileSaveAsPdf 파라미터 설정
897
+ self.hwp.HParameterSet.HFileOpenSave.filename = path # 저장할 PDF 파일 절대경로
898
+ self.hwp.HParameterSet.HFileOpenSave.Format = "PDF" # 파일 형식
899
+ self.hwp.HParameterSet.HFileOpenSave.Attributes = 16384 # 속성 설정
900
+
901
+ # 설정된 FileSaveAsPdf 작업 실행
902
+ return self.hwp.HAction.Execute("FileSaveAsPdf", self.hwp.HParameterSet.HFileOpenSave.HSet)
903
+
904
+ def set_password(self, password: str):
905
+ """
906
+ HWP 문서에 파일 비밀번호를 설정하는 메서드.
907
+ 한글 2024이상 버젼에서 적용된다.
908
+
909
+ Parameters:
910
+ password (str): 설정할 비밀번호.
911
+ """
912
+ # FilePassword 작업 기본 설정 가져오기
913
+ self.hwp.HAction.GetDefault("FilePassword", self.hwp.HParameterSet.HPassword.HSet)
914
+
915
+ # FilePassword 파라미터 설정
916
+ self.hwp.HParameterSet.HPassword.string = password # 비밀번호 설정
917
+ self.hwp.HParameterSet.HPassword.Level = 1 # 보안 수준 설정
918
+ self.hwp.HParameterSet.HPassword.DialogType = 2 # 대화 상자 유형 설정
919
+
920
+ # 설정된 FilePassword 작업 실행
921
+ return self.hwp.HAction.Execute("FilePassword", self.hwp.HParameterSet.HPassword.HSet)
922
+
923
+
924
+
925
+ def set_font_style(self, fontstyle: str = "굴림"):
926
+ '블럭으로 잡은 글자의 글꼴을 변경한다.'
927
+
928
+ # 문자 모양 설정을 초기화합니다.
929
+ self.hwp.HAction.GetDefault("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
930
+
931
+ # 글꼴 설정
932
+ self.hwp.HParameterSet.HCharShape.FaceNameUser = fontstyle
933
+ self.hwp.HParameterSet.HCharShape.FontTypeUser = self.hwp.FontType("TTF")
934
+ self.hwp.HParameterSet.HCharShape.FaceNameSymbol = fontstyle
935
+ self.hwp.HParameterSet.HCharShape.FontTypeSymbol = self.hwp.FontType("TTF")
936
+ self.hwp.HParameterSet.HCharShape.FaceNameOther = fontstyle
937
+ self.hwp.HParameterSet.HCharShape.FontTypeOther = self.hwp.FontType("TTF")
938
+ self.hwp.HParameterSet.HCharShape.FaceNameJapanese = fontstyle
939
+ self.hwp.HParameterSet.HCharShape.FontTypeJapanese = self.hwp.FontType("TTF")
940
+ self.hwp.HParameterSet.HCharShape.FaceNameHanja = fontstyle
941
+ self.hwp.HParameterSet.HCharShape.FontTypeHanja = self.hwp.FontType("TTF")
942
+ self.hwp.HParameterSet.HCharShape.FaceNameLatin = fontstyle
943
+ self.hwp.HParameterSet.HCharShape.FontTypeLatin = self.hwp.FontType("TTF")
944
+ self.hwp.HParameterSet.HCharShape.FaceNameHangul = fontstyle
945
+ self.hwp.HParameterSet.HCharShape.FontTypeHangul = self.hwp.FontType("TTF")
946
+
947
+ # 설정한 문자 모양을 실행하여 적용합니다.
948
+ return self.hwp.HAction.Execute("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
949
+
950
+ def set_font_size(self, height : int = 10):
951
+ '블럭으로 잡은 글자의 크기를 변경한다'
952
+
953
+ # 문자 모양 설정을 초기화합니다.
954
+ self.hwp.HAction.GetDefault("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
955
+
956
+ # 글꼴 유형 및 크기 설정
957
+ self.hwp.HParameterSet.HCharShape.FontTypeUser = self.hwp.FontType("TTF")
958
+ self.hwp.HParameterSet.HCharShape.FontTypeSymbol = self.hwp.FontType("TTF")
959
+ self.hwp.HParameterSet.HCharShape.FontTypeOther = self.hwp.FontType("TTF")
960
+ self.hwp.HParameterSet.HCharShape.FontTypeJapanese = self.hwp.FontType("TTF")
961
+ self.hwp.HParameterSet.HCharShape.FontTypeHanja = self.hwp.FontType("TTF")
962
+ self.hwp.HParameterSet.HCharShape.FontTypeLatin = self.hwp.FontType("TTF")
963
+ self.hwp.HParameterSet.HCharShape.FontTypeHangul = self.hwp.FontType("TTF")
964
+
965
+ # 글꼴 크기 설정
966
+ self.hwp.HParameterSet.HCharShape.Height = self.hwp.PointToHwpUnit(float(height))
967
+
968
+ # 설정한 문자 모양을 실행하여 적용합니다.
969
+ self.hwp.HAction.Execute("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
970
+
971
+ # 문자 모양 설정을 다시 초기화합니다.
972
+ self.hwp.HAction.GetDefault("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
973
+
974
+ # 동일한 글꼴 유형 설정
975
+ self.hwp.HParameterSet.HCharShape.FontTypeUser = self.hwp.FontType("TTF")
976
+ self.hwp.HParameterSet.HCharShape.FontTypeSymbol = self.hwp.FontType("TTF")
977
+ self.hwp.HParameterSet.HCharShape.FontTypeOther = self.hwp.FontType("TTF")
978
+ self.hwp.HParameterSet.HCharShape.FontTypeJapanese = self.hwp.FontType("TTF")
979
+ self.hwp.HParameterSet.HCharShape.FontTypeHanja = self.hwp.FontType("TTF")
980
+ self.hwp.HParameterSet.HCharShape.FontTypeLatin = self.hwp.FontType("TTF")
981
+ self.hwp.HParameterSet.HCharShape.FontTypeHangul = self.hwp.FontType("TTF")
982
+
983
+ # 설정한 문자 모양을 실행하여 적용합니다.
984
+ return self.hwp.HAction.Execute("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
985
+
986
+
987
+ # 기타 부품들
988
+
989
+ def key_indicator(self) -> tuple:
990
+ """
991
+ 상태 바의 정보를 얻어온다.
992
+ (캐럿이 표 안에 있을 때 셀의 주소를 얻어오는 거의 유일한 방법이다.)
993
+
994
+ :return:
995
+ 튜플(succ, seccnt, secno, prnpageno, colno, line, pos, over, ctrlname)
996
+ succ: 성공하면 True, 실패하면 False (항상 True임..)
997
+ seccnt: 총 구역
998
+ secno: 현재 구역
999
+ prnpageno: 쪽
1000
+ colno: 단
1001
+ line: 줄
1002
+ pos: 칸
1003
+ over: 삽입모드 (True: 수정, False: 삽입)
1004
+ ctrlname: 캐럿이 위치한 곳의 컨트롤이름
1005
+
1006
+ :example:
1007
+ >>> # 현재 셀 주소(표 안에 있을 때)
1008
+ >>> from pyhwpx import Hwp
1009
+ >>> hwp = Hwp()
1010
+ >>> hwp.KeyIndicator()[-1][1:].split(")")[0]
1011
+ "A1"
1012
+ """
1013
+ return self.hwp.KeyIndicator()
1014
+
1015
+ def is_cell(self):
1016
+ """
1017
+ 캐럿이 현재 표 안에 있는지 알려주는 메서드
1018
+ :return:
1019
+ 표 안에 있으면 True, 그렇지 않으면 False를 리턴
1020
+ """
1021
+ if self.key_indicator()[-1].startswith("("):
1022
+ return True
1023
+ else:
1024
+ return False
1025
+
1026
+ @property
1027
+ def SelectionMode(self):
1028
+ """
1029
+ 현재 선택모드가 어떤 상태인지 리턴한다.
1030
+ :return:
1031
+ """
1032
+ return self.hwp.SelectionMode
1033
+
1034
+ @property
1035
+ def HParameterSet(self):
1036
+ """
1037
+ 한/글에서 실행되는 대부분의 액션에 필요한
1038
+ 다양한 파라미터셋을 제공해주는 속성.
1039
+ 사용법은 아래와 같다.
1040
+
1041
+ >>> from pyhwpx import Hwp
1042
+ >>> hwp = Hwp()
1043
+ >>> pset = hwp.HParameterSet.HInsertText
1044
+ >>> pset.Text = "Hello world!"
1045
+ >>> hwp.HAction.Execute("InsertText", pset.HSet)
1046
+
1047
+ :return:
1048
+ """
1049
+ return self.hwp.HParameterSet
1050
+
1051
+ @property
1052
+ def HAction(self):
1053
+ """
1054
+ 한/글의 액션을 설정하고 실행하기 위한 속성.
1055
+ GetDefalut, Execute, Run 등의 메서드를 가지고 있다.
1056
+ :return:
1057
+ """
1058
+ return self.hwp.HAction
1059
+
1060
+ def goto_printpage(self, page_num: int = 1):
1061
+ """
1062
+ 인쇄페이지 기준으로 해당 페이지로 이동
1063
+ 1페이지의 page_num은 1이다.
1064
+ :param page_num: 이동할 페이지번호
1065
+ :return: 성공시 True, 실패시 False를 리턴
1066
+ """
1067
+ pset = self.hwp.HParameterSet.HGotoE
1068
+ self.hwp.HAction.GetDefault("Goto", pset.HSet)
1069
+ pset.HSet.SetItem("DialogResult", page_num)
1070
+ pset.SetSelectionIndex = 1
1071
+ return self.hwp.HAction.Execute("Goto", pset.HSet)
1072
+
1073
+ def MovePageUp(self):
1074
+ """
1075
+ 뒤 페이지의 시작으로 이동. 현재 탑레벨 리스트가 아니면 탑레벨 리스트로 빠져나온다.
1076
+ """
1077
+ cwd = self.get_pos()
1078
+ self.hwp.HAction.Run("MovePageUp")
1079
+ if self.get_pos()[0] != cwd[0] or self.get_pos()[1:] != cwd[1:]:
1080
+ return True
1081
+ else:
1082
+ return False
1083
+
1084
+ def MovePageDown(self):
1085
+ """
1086
+ 앞 페이지의 시작으로 이동. 현재 탑레벨 리스트가 아니면 탑레벨 리스트로 빠져나온다.
1087
+ """
1088
+ cwd = self.get_pos()
1089
+ self.hwp.HAction.Run("MovePageDown")
1090
+ if self.get_pos()[0] != cwd[0] or self.get_pos()[1:] != cwd[1:]:
1091
+ return True
1092
+ else:
1093
+ return False
1094
+
1095
+ @property
1096
+ def current_printpage(self):
1097
+ """
1098
+ 현재 쪽번호를 리턴.
1099
+ 1페이지에 있다면 1을 리턴한다.
1100
+ 새쪽번호가 적용되어 있다면
1101
+ 수정된 쪽번호를 리턴한다.
1102
+ :return:
1103
+ """
1104
+ return self.hwp.XHwpDocuments.Active_XHwpDocument.XHwpDocumentInfo.CurrentPrintPage
1105
+
1106
+ def mili_to_hwp_unit(self, mili):
1107
+ return self.hwp.MiliToHwpUnit(mili=mili)
1108
+
1109
+ def get_pos(self) -> tuple[int]:
1110
+ """
1111
+ 캐럿의 위치를 얻어온다.
1112
+ 파라미터 중 리스트는, 문단과 컨트롤들이 연결된 한/글 문서 내 구조를 뜻한다.
1113
+ 리스트 아이디는 문서 내 위치 정보 중 하나로서 SelectText에 넘겨줄 때 사용한다.
1114
+ (파이썬 자료형인 list가 아님)
1115
+
1116
+ :return:
1117
+ (List, para, pos) 튜플.
1118
+ list: 캐럿이 위치한 문서 내 list ID(본문이 0)
1119
+ para: 캐럿이 위치한 문단 ID(0부터 시작)
1120
+ pos: 캐럿이 위치한 문단 내 글자 위치(0부터 시작)
1121
+
1122
+ """
1123
+ return self.hwp.GetPos()
1124
+
1125
+ def set_pos_by_set(self, disp_val):
1126
+ """
1127
+ 캐럿을 ParameterSet으로 얻어지는 위치로 옮긴다.
1128
+
1129
+ :param disp_val:
1130
+ 캐럿을 옮길 위치에 대한 ParameterSet 정보
1131
+
1132
+ :return:
1133
+ 성공하면 True, 실패하면 False
1134
+
1135
+ :example:
1136
+ >>> start_pos = hwp.GetPosBySet() # 현재 위치를 저장하고,
1137
+ >>> hwp.set_pos_by_set(start_pos) # 특정 작업 후에 저장위치로 재이동
1138
+ """
1139
+ return self.hwp.SetPosBySet(dispVal=disp_val)
1140
+
1141
+ def get_cur_field_name(self, option=0):
1142
+ """
1143
+ 현재 캐럿이 위치하는 곳의 필드이름을 구한다.
1144
+ 이 함수를 통해 현재 필드가 셀필드인지 누름틀필드인지 구할 수 있다.
1145
+ 참고로, 필드 좌측에 커서가 붙어있을 때는 이름을 구할 수 있지만,
1146
+ 우측에 붙어 있을 때는 작동하지 않는다.
1147
+ GetFieldList()의 옵션 중에 hwpFieldSelection(=4)옵션은 사용하지 않는다.
1148
+
1149
+
1150
+ :param option:
1151
+ 다음과 같은 옵션을 지정할 수 있다.
1152
+ 0: 모두 off. 생략하면 0이 지정된다.
1153
+ 1: 셀에 부여된 필드 리스트만을 구한다. hwpFieldClickHere와는 함께 지정할 수 없다.(hwpFieldCell)
1154
+ 2: 누름틀에 부여된 필드 리스트만을 구한다. hwpFieldCell과는 함께 지정할 수 없다.(hwpFieldClickHere)
1155
+
1156
+ :return:
1157
+ 필드이름이 돌아온다.
1158
+ 필드이름이 없는 경우 빈 문자열이 돌아온다.
1159
+ """
1160
+ return self.hwp.GetCurFieldName(option=option)
1161
+
1162
+ def set_pos(self, list, para, pos):
1163
+ """
1164
+ 캐럿을 문서 내 특정 위치로 옮긴다.
1165
+ 지정된 위치로 캐럿을 옮겨준다.
1166
+
1167
+ :param list:
1168
+ 캐럿이 위치한 문서 내 list ID
1169
+
1170
+ :param para:
1171
+ 캐럿이 위치한 문단 ID. 음수거나, 범위를 넘어가면 문서의 시작으로 이동하며, pos는 무시한다.
1172
+
1173
+ :param pos:
1174
+ 캐럿이 위치한 문단 내 글자 위치. -1을 주면 해당문단의 끝으로 이동한다.
1175
+ 단 para가 범위 밖일 경우 pos는 무시되고 문서의 시작으로 캐럿을 옮긴다.
1176
+
1177
+ :return:
1178
+ 성공하면 True, 실패하면 False
1179
+ """
1180
+ self.hwp.SetPos(List=list, Para=para, pos=pos)
1181
+ if (list, para) == self.get_pos()[:2]:
1182
+ return True
1183
+ else:
1184
+ return False
1185
+
1186
+ def find_ctrl(self):
1187
+ return self.hwp.FindCtrl()
1188
+
1189
+ def TableCellBlock(self):
1190
+ """
1191
+ 셀 블록
1192
+ """
1193
+ # return self.hwp.HAction.Run("TableCellBlock")
1194
+ pset = self.HParameterSet.HInsertText
1195
+ self.HAction.GetDefault("TableCellBlock", pset.HSet)
1196
+ return self.HAction.Execute("TableCellBlock", pset.HSet)
1197
+
1198
+ def TableColBegin(self):
1199
+ """
1200
+ 셀 이동: 열 시작
1201
+ """
1202
+ return self.hwp.HAction.Run("TableColBegin")
1203
+
1204
+ def TableColPageUp(self):
1205
+ """
1206
+ 셀 이동: 페이지 업
1207
+ """
1208
+ return self.hwp.HAction.Run("TableColPageUp")
1209
+
1210
+ def get_selected_range(self):
1211
+ """
1212
+ 선택한 범위의 셀주소를
1213
+ 리스트로 리턴함
1214
+ """
1215
+ if not self.is_cell():
1216
+ raise AttributeError("캐럿이 표 안에 있어야 합니다.")
1217
+ pset = self.HParameterSet.HFieldCtrl
1218
+ self.HAction.GetDefault("TableFormula", pset.HSet)
1219
+ return pset.Command[2:-1].split(",")
1220
+
1221
+ def GetTextFile(self, format="UNICODE", option=""):
1222
+
1223
+ return self.hwp.GetTextFile(Format=format, option=option)
1224
+
1225
+ @staticmethod
1226
+ def cell_to_index(cell):
1227
+ """ 엑셀 셀 주소를 행과 열 인덱스로 변환"""
1228
+ column = ord(cell[0]) - ord('A') # 열 인덱스 (0-based)
1229
+ row = int(cell[1:]) - 1 # 행 인덱스 (0-based)
1230
+ return row, column
1231
+
1232
+ @staticmethod
1233
+ def crop_data_from_selection(data, selection):
1234
+ """ 리스트 a의 셀 주소를 바탕으로 데이터 범위를 추출"""
1235
+ if not selection:
1236
+ return []
1237
+
1238
+ # 셀 주소를 행과 열 인덱스로 변환
1239
+ indices = [HWP.cell_to_index(cell) for cell in selection]
1240
+
1241
+ # 범위 계산
1242
+ min_row = min(idx[0] for idx in indices)
1243
+ max_row = max(idx[0] for idx in indices)
1244
+ min_col = min(idx[1] for idx in indices)
1245
+ max_col = max(idx[1] for idx in indices)
1246
+
1247
+ # 범위 추출
1248
+ result = []
1249
+ for row in range(min_row, max_row + 1):
1250
+ result.append(data[row][min_col:max_col + 1])
1251
+
1252
+ return result
1253
+
1254
+ def get_active_window_title(self): # 활성화된 window창의 제목을 리턴한다.
1255
+ hwnd = win32gui.GetForegroundWindow()
1256
+ window_title = win32gui.GetWindowText(hwnd)
1257
+ return window_title
1258
+
1259
+
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: hwpwings
3
+ Version: 0.1.0
4
+ Summary: 파이썬으로 한글 문서를 제어할 수 있는 두번째 라이브러리입니다.
5
+ Home-page: https://www.youtube.com/@%ED%95%9C%ED%8C%8C%EC%97%91%EC%85%80
6
+ Author: 한파엑셀
7
+ Author-email: hpxl0518@gmail.com
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: pywin32>=302
11
+ Requires-Dist: pandas>=1.0.0
12
+ Requires-Dist: lxml>=4.0.0
@@ -0,0 +1,9 @@
1
+ README.md
2
+ setup.py
3
+ hwpwings/__init__.py
4
+ hwpwings/hwp.py
5
+ hwpwings.egg-info/PKG-INFO
6
+ hwpwings.egg-info/SOURCES.txt
7
+ hwpwings.egg-info/dependency_links.txt
8
+ hwpwings.egg-info/requires.txt
9
+ hwpwings.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ pywin32>=302
2
+ pandas>=1.0.0
3
+ lxml>=4.0.0
@@ -0,0 +1 @@
1
+ hwpwings
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="hwpwings",
5
+ version="0.1.0",
6
+ author="한파엑셀",
7
+ author_email="hpxl0518@gmail.com",
8
+ description="파이썬으로 한글 문서를 제어할 수 있는 두번째 라이브러리입니다.",
9
+ long_description=open("README.md", encoding="utf-8").read(),
10
+ long_description_content_type="text/markdown",
11
+ url="https://www.youtube.com/@%ED%95%9C%ED%8C%8C%EC%97%91%EC%85%80", # YouTube URL
12
+ packages=find_packages(),
13
+
14
+ python_requires=">=3.6",
15
+ install_requires=[
16
+ "pywin32>=302", # win32com, pythoncom
17
+ "pandas>=1.0.0", # pandas 모듈
18
+ "lxml>=4.0.0", # XML 파싱에 사용하는 대안
19
+ ],
20
+ )