교무업무자동화

[hwp + python 업무자동화] 졸업사정안 자동화 #3 KeyIndecator의 활용

dinggle 2023. 12. 15. 11:29

2023.12.07 - [교무업무자동화] - [hwp + python 업무자동화] 졸업 사정안 자동화 프로그램 #2

사정안 양식 기초작업 - Field와 KeyIndecator의 활용

< 목차 >
1. 한글문서에서 Field의 활용
2. 누름틀로 Field 생성
3. 표에서 Field 생성
4. 키인디케이터를 활용한 FieldName 자동화

 

1. 한글문서에서 Field의 활용

 

나는 파이썬을 기초적인 문법부터 쌓아 올라가는 것보다는 프로젝트 수행 과정을 통해서 필요한 기능이나 구문, 공식 등을 하나씩 찾아서 조합하고, 그 과정에서 처음 접하게 되는 것들을 중심으로 학습과 프로젝트 수행을 병행하는 백워드 방식을 선호한다. 

 

이 자동화 프로그램은 학습을 1차적인 목표로 하고, 원하는 기능을 구현하기 위한 구성요소들을 하나씩 익히면서 약 2개월 정도 진행한 결과물이다. 그렇기 때문에 각 기능의 통합적 이해는 다소 부족할 수 있지만 프로젝트 과정에서 꼭 필요한 기능을 초보의 시선으로 정리해 보았다. 

 

한컴에서 제공하는 HwpCtrl API 문서를 보면 한글자동화를 위한 컨트롤과 메서드를 제공하고 있다. 사실 이 자동화 프로젝트에서는 HwpCtrl Object의 Field컨트롤을 익히는 것이 거의 대부분이다.

 

HwpCtrl-API.pdf
1.23MB

 

기능 설명
Action Object 액션은 사용자가 하나의 단위 기능으로 인식하는 한글의 각각의 기능
메뉴, 툴바, 단축키를 통해서 실행할 수 있는 하나의 기능
CtrlCode Object 문서 내부의 표, 각주 등의 컨트롤(특수 문자)을 나타내는 오브젝트
HwpCtrl Object 한글 컨트롤에서 다른 오브젝트의 생성, 툴바 제어 등의 컨트롤 전반적인 기능을 수행
CreatField, GetFieldList, GetFieldText, MoveToField, PutFieldText 를 포함
HwpCtrl Event 사용자의 입력에 대한 이벤트를 감지하여 이벤트 헨들러에 전달
Parameterset Object 오브젝트 간, 액션의 실행에 대한 정보를 주고받을 수 있는 오브젝트
ParameterArray parameter set의 아이템으로 배열을 표현하는 데 사용
HwpMenu HwpCtrl에서 컨텍스트 메뉴를 다루는 객체

 

첨부 문서에는 C++과 javascript를 기준으로 구문 예시가 나와 있어 낯설어 보이지만 파이썬 문법과 크게 다르진 않다. 또 파이썬에서 작동하는 방식과 구체적인 매개변수의 입력 방식은 한글 프로그램의 스크립트 매크로를 확인하면 어느 정도 파악이 가능하다. 그리고 앞에서 말한 일상의 코딩(https://martinii.fun/) 블로그를 참고하면 기본적인 이해가 가능하다. 

 

여러 시행착오를 거치면서 한글문서 자동화를 위해서 가장 먼저 고려해야 할 사항은 입력할 정보의 위치에 누름틀 Field를 생성하고, 각 FieldName은 고유한 값을 부여하여 각 Field를 구분하여 입력하는 것이 적합하다는 결론을 내렸다. 

 

이때 FieldName을 모두 동일하게 하는 것도 가능하지만(메일머지 처리방식에 적합) 일반적인 보고서 양식에서는 적합하지는 않다. 이렇게 한 다면 동일한 필드 이름을 가진 누름틀의 index값이 별도로 필요하기 때문이다. 

 

또 상황에 따라서 전체 필드를 모두 한번에 업데이트 할 수도 있고, 일부 필드만 선택하여 업데이트를 할 수도 있기 때문에 각 필드명을 고유한 값을 가지도록 부여하고, 상황에 따라 데이터를 선택적으로 입력하는 것이 적합하다고 본다. 

 

이런 방식을 취하기 위해서는 한글 파일에 값이 입력될 부분에 누름틀 필드를 사전에 생성해 두어야 한다. 그리고 표와 같이 데이터를 입력해야 할 필드가 여러 개 라면 수작업으로 이를 생성하는 것은 비효율적이기 때문에 자동화 코드를 통해 생성하였다. 

 

 

2. 누름틀로 Field 생성

사정안 양식에서 표가 아닌 본문 중에 데이터를 입력할 곳은 누름틀로 필드를 생성한다. 사정안 양식에서 본문 영역에서 학급, 교사명, 사정인원, 개근 대상자 인원, 정근 대상자 인원, 모범상 대상자 인원, 공로상 대상자 인원을 누름틀로 처리할 것이다. 

 

먼저 입력할 정보의 위치에 Ctrl + K, E 를 통해 누름틀을 생성한다

 

 

옵션 설명
입력할 내용의 안내문* 데이터의 예시 값을 입력, 실제 입력될 데이터의 샘플 값을 입력하는 것을 추천한다. 그래야 값을 입력했을 때의 글자 수, 여백 등을 고려하기 수월하다. 
메모내용 말 그대로 메모. 생략 가능
필드 이름* 한글문서 자동화에서 사용할 각 필드의 고유값으로 활용하는 하는 부분
양식 모드에서 편집 가능 한글의 편집모드는 읽기 전용, 일반 편집 모드, 양식 모드가 있음.
셀과 누름틀 중 양식 모드에서 편집 가능 속성을 가진 항목만 편집이 가능하도록 하는 옵션
양식 모드는 나도 사용해 본 적이 없기 때문에 무시해도 될 듯 싶다.

 

 

여기에서 중요한 것은 "필드 이름"이다. 

 

실제 입력될 데이터의 속성을 고려하여 이해하기 쉽게 부여하고, 일정한 규칙성을 갖도록 하는 것이 좋다.  내 경우에는 필드 이름의 앞부분은 필드 그룹명으로 붙이고, 뒷부분은 필드의 속성에 따라 부여했다. 

 

누름틀 필드명 누름틀 필드명
info_class 교사명 info_teacher
사정인원 inout_cnt 3개년 개근 대상자 수 all_cnt
3개년 정근 대상자 수 fall_cnt 모범상 대상자 수 mo_cnt
공로상 대상자 수 gong_cnt    

 

입력할 데이터의 유형에 따라 필드의 그룹명은 아래와 같이 구분하였다. 

3. 표에서 Field 생성

표에서 각 셀에도 Field를 생성할 수 있는데 누름틀 Field와 동일한 기능을 수행한다.

셀의 필드 속성은 [표, 셀 속성 > 셀 탭 선택 > 필드 > 필드 이름]으로 접근할 수 있다. 

 

 

4. KeyIndecator 를 활용한 Field Name 생성 자동화

한글문서 안에 표가 한 개이고 셀도 많지 않다면 셀 속성창을 열어서 하나씩 입력하는 것이 쉬울 수 있다. 하지만 대부분 보고서 양식을 보면 복잡한 형태의 표와 많은 셀이 존재한다. 

 

한글문서의 표에서 셀은 엑셀과 같이 기본적으로 A1, A2와 같이 셀주소를 가지고 있다. 표에서 첫 번째 셀을 선택하면 아래 그림의 상태바에서 컨트롤이름에서 (A1):문자입력 이라고 표시가 되고, A1은 엑셀과 같은 셀주소를 나타낸다. 하지만 엑셀과 달리 한글에서는 표에 이름을 부여할 수 있는 속성이 없기 때문에 어떤 표에 A1 셀인지를 정확하게 알 수 없다. 

 

 

여러 번의 시행착오 끝에  내가 원하는 표의 필드에 접근하기 위해서 필드 이름을 "inout_A1", "all_B2"와 같이 필드 그룹과 셀주소로 묶어서 부여하는 방법을 택했다. 이렇게 하면 필드 이름을 슬라이싱하여 내가 원하는 표와 셀주소를 변별할 수 있다. 

 

자동화를 위해서는 먼저 KeyIndecator 에 대한 이해가 필요하다.

한글문서에서 현재 입력 위치를 표시하는 깜빡이는 세로바를 캐럿이라고 하는데 이 캐럿의 현재 위치에 대한 정보가 상태바에 표시된다. 그리고 이 상태바에는 다양한 정보가 제시된다. 

 

hwpAPI의 메서드 중 KeyINdecator를 통해 이 상태바의 정보에 접근하여 얻을 수 있는 정보는 다음과 같다. 

매개변수 총구역 현재구역 삽입모드 컨트롤이름
1/1구역 1/1쪽 1단 1줄 2칸 삽입 (A1): 문자입력[inout_A1]

 

 

파이썬으로 구성할 대략적은 자동화 프로세스는 다음과 같다. 

  • hwpAPI의 인디게이터 컨트롤을 통해 문서 내의 개체를 탐색
  • 개체가 표일 경우 표의 인덱스 번호를 부여하고, 첫 번째 셀로 이동
  • 인디게이터를 통해 셀주소를 확인
  • 표의 인덱스에 해당하는 표의 이름과 셀주소를 조합하여 필드 이름 생성
  • 다음 셀로 이동
  • 다음 셀이 존재하지 않으면 다음 표로 이동
  • 위의 작업 반복

 

win32com(pywin32) 라이브러리는 윈도우 프로그램을 제어하기 위한 라이브러리이다. 한글프로그램을 win32com 라이브러리를 통해 제어해야 하기 때문에 먼저 win32com을 import 하고, 현재 작업폴더의 경로를 구하기 위해  os모듈을 import한다. 

import win32com.client as win32
import os

 

 

os.getcwd()를 통해 현재 작업 경로를 변수 path에 저장한다. 

path = os.getcwd()

 

 

표의 인덱스 정보를 저장하기 위해서 table_i 변수를 초기화하고, 삽입된 표의 이름 역할을 할 필드 그룹을 리스트의 형태로 저장한다.  문서 내에서 개체를 탐색하고 만약 컨트롤이 표일 경우 인덱스 번호를 부여하고, 리스트의 필드 그룹명과 매칭하여 각 필드 이름의 접두사로 활용할 것이다. 

#테이블 카운팅
table_i = 0
table = ['info', 'all', 'fall', 'mo', 'gong']

 

 

한글프로그램을 제어하기 위해서 win32.gencache.EnsureDispatch("HWPFrame.HwpObject")을 hwp 변수에 저장한다. 수정할 한글파일의 양식은 현재 경로의 하위폴더인 HwpControl에 저장되어 있기 때문에 path 경로 뒤에 추가하고, 양식이 저장된 한글파일을 실행한다. 

### 한글파일 작업
#한글 실행
hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")

#수정할 한글파일 열기
hwp.Open(path + r"\HwpControl\졸업사정안 통계표 및 포상집계표.hwp")

#백그라운드 숨김 해제
hwp.XHwpWindows.Item(0).Visible = True

 

 

 Action Object 중 하나인 Run은 한글에서 자주 사용되는 명령이나 단축키와 같은 하나의 단위로 이루어진 기능이다.

캐럿의 위치를 한글 문서의 맨 앞으로 이동하는 단축키인 Ctrl + Pageup 을 실행하고, 스크립트 코드를 확인하면 Run으로 시작하는 메서드를 확인할 수 있다. 

 

문서의 맨 처음으로 캐럿을 이동하고 작업을 시작하는 이유는 한글문서를 실행하면 캐럿 마지막을 작업한 위치에 표시가 된다. 자동화 코드는 캐럿의 현재 위치부터 아래쪽으로 순차적으로 이동하면서 작업이 이루어진다. 그렇기 때문에 캐럿의 위치를 문서의 맨 앞으로 이동시키는 것이 코드를 실행하는 것이 작업속도를 올리고, 변수를 제어하기가 쉽다.

#문서의 처음으로 이동
hwp.Run("MoveDocBegin")

 

 

테이블 탐색 함수

find_select_table 함수를 정의하고, 테이블의 인덱스 값을 담기 위한 전역변수 table_i를 선언한다. 

while 반복문을 통해 문서에서 개체를 탐색하면서 KeyIndicator로 상태바의 정보를 find_table 변수에 저장한다.  find_table에 저장된 값을 print 해 보면 다음과 같은 튜플 값을 얻을 수 있다. 

(True, 1, 1, 1, 1, 4, 1, 0, '표')

 

위의 튜플에서 가장마지막 항목 find_table[-1] 이 "표"가 아니라면 SelectctrlFront로 다음 개체를 탐색한다. SelectctrlFront는 한글문서에서 단축키 Ctrl+F11과 동일한 기능이다.

이때 if문을 통해 만약 튜플의 마지막 요소가 "표"라면 첫번째 셀을 선택 ShapeObjTableSelcell 한다. 그리고 개체선택을 풀어주기 위해서 Run("Cancel") 한다. 

 

#테이블 탐색 함수
def find_select_table():
    global table_i
        #선택한 개체가 표가 아닐 경우 다음 개체를 탐색
    while True:
            find_table = hwp.KeyIndicator()
            #print(find_table) 상태바의 정보 확인
            if find_table[-1] != "표":
                    hwp.Run("SelectCtrlFront")

                    next_table = hwp.KeyIndicator()
                    if find_table == next_table:
                            hwp.Run("Cancel")
                            break
                    
            else:
                    #표가 존재한다면 첫번째 셀을 선택
                    hwp.Run("ShapeObjTableSelCell")
                    hwp.Run("Cancel")
                    table_i += 1
                    break

 

 

셀 주소 생성 함수

hwp.KeyIndicator()[-1][1:].split(")")[0]

표에서 셀마다 고유한 필드 이름을 부여하기 위해 table 리스트에서 표의 인덱스와 같은 요소를 가져오고, 상태바에서 셀주소가 포함되어 있는 가장 마지막 항목 hwp.Keyindicator()[-1] 을 선택한다. 

(A1): 문자입력

 

 hwp.KeyIndicator()[-1][1:].split(")")[0]

위에서 "A1"이 셀주소이기 때문에 슬라이싱을 통해 0번째 문자열인 " ( "을 제외하고 두번째 문자열 "A"부터 끝까지 문자열을 슬라이싱한다. 여기까지 값을 출력하면 " A1): 문자입력"이 출력된다. 

 

 hwp.KeyIndicator()[-1][1:].split(")")[0]

위에서 " ) "를 기준으로 분할(split)하여 첫번째 요소[0]를 가져오면 "A1"만 출력할 수 있다. 

 

table[table_i-1]으로 가져온 필드그룹명과 위에서 가져온 셀주소를 조합하여 "inout_A1", "inout_A2"와 같은 형태로 필드 이름을 생성한다. 

#셀주소 추출 함수
def GetCellAddress():
        return table[table_i-1] + "_" + hwp.KeyIndicator()[-1][1:].split(")")[0]

 

 

마지막으로 필드 이름을 입력하기 위한 부분이다. 이 부분은 셀의 속성창에 접근하여 필드이름을 입력하는 과정을 파이썬으로 구현해야 하는데 이때 스크립트 매크로를 활용하여 코드를 확인하면 된다. 

 

  • 한글문서에서 임의의 표를 만들고, 캐럿을 첫 번째 셀에 위치시킨다.
  • 도구-스크립트 매크로 - 매크로 정의를 선택하여 새로운 스크립트를 정의한다. (Alt + Shift + H)
  • 표/셀 속성 단축키(Ctrl+N,K)를 누르고, 필드이름을 입력한다. 
  • 스크립트 매크로를 중지한다. (Alt + Shift + X)
  • 스크립트 매크로를 실행하고, 코드편집을 선택한다. 
function OnScriptMacro_script4()
{
	HAction.GetDefault("TablePropertyDialog", HParameterSet.HShapeObject.HSet);
	with (HParameterSet.HShapeObject)
	{
		HorzRelTo = HorzRel("Para");
		HSet.SetItem("ShapeType", 3);
		HSet.SetItem("ShapeCellSize", 0);
		ShapeTableCell.CellCtrlData.Name = "inout_A1";
	}
	HAction.Execute("TablePropertyDialog", HParameterSet.HShapeObject.HSet);

 

 

위의 코드가 실행되고 나면 상태바를 살펴보면 아래와같이 컨트롤 이름이 변경되고 필드 이름은 [  ] 안에 표시가 된다. 

 

스크립트 매크로를 파이썬에서 인식이 가능하도록 편집하려면 각 구문 앞에 변수 hwp를 붙이고, 불필요한 구문은 삭제한 후, with문은 풀어주면 된다. ShapeTableCell.CellctrlData.Name 이 필드 이름을 입력하는 구문이기 때문에 여기에 셀주소추출 함수를 붙여주면 된다. 

 #셀 필드명 입력
hwp.HAction.GetDefault("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)
hwp.HParameterSet.HShapeObject.ShapeTableCell.Editable = 1
hwp.HParameterSet.HShapeObject.ShapeTableCell.CellCtrlData.name = GetCellAddress()
hwp.HAction.Execute("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)

 

완성된 구문은 다음과 같다. 

#테이블 필드 생성
while True:
        now_tbl = hwp.KeyIndicator()
        hwp.Run("SelectCtrlFront")
        mov_tbl = hwp.KeyIndicator()
        
        if now_tbl == mov_tbl:
                break
        else:
                find_select_table()
                while True:
                        
                        #셀일 경우 주소줄에서 "문자입력"으로 표시되는 부분을 추출
                        cell_state = hwp.KeyIndicator()[-1].split(':')[-1].strip()
                        
                        if cell_state == "문자 입력":
                                #셀 필드명 입력
                                hwp.HAction.GetDefault("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)
                                hwp.HParameterSet.HShapeObject.ShapeTableCell.Editable = 1
                                hwp.HParameterSet.HShapeObject.ShapeTableCell.CellCtrlData.name = GetCellAddress()
                                hwp.HAction.Execute("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)
 
                                #현재 셀 위치 정보 확인
                                cell_now = GetCellAddress()
                                # 오른쪽으로 셀 이동
                                hwp.HAction.Run("TableRightCell")
                                #이동한 셀의 위치 정보 확인
                                cell_move = GetCellAddress()
                                #이동 전의 셀과 이동 후의 셀정보가 같다면 
                                if cell_now == cell_move:
                                        hwp.Run("Cancel")
                                        hwp.Run("SelectCtrlFront")

                                        break
                        else:
                                break

 

 

### 전체코드
import win32com.client as win32
import os

path = os.getcwd()


### 한글파일 작업
#한글 실행
hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")

#보안모듈 활성화
hwp.RegisterModule("FilePathCheckDLL", "SecurityModule")

#수정할 한글파일 열기
hwp.Open(path + r"\HwpControl\졸업사정안 통계표 및 포상집계표.hwp")


#백그라운드 숨김 해제
hwp.XHwpWindows.Item(0).Visible = True  


#문서의 처음으로 이동
hwp.Run("MoveDocBegin")

#테이블 카운팅
table_i = 0
table = ['total', 'award']

#테이블 탐색 함수
def find_select_table():
    global table_i
        #선택한 개체가 표가 아닐 경우 다음 개체를 탐색
    while True:
            find_table = hwp.KeyIndicator()
            if find_table[-1] != "표":
                    hwp.Run("SelectCtrlFront")

                    next_table = hwp.KeyIndicator()
                    if find_table == next_table:
                            hwp.Run("Cancel")
                            break
                    
            else:
                    #표가 존재한다면 첫번째 셀을 선택
                    hwp.Run("ShapeObjTableSelCell")
                    hwp.Run("Cancel")
                    table_i += 1
                    break

#셀주소 추출 함수
def GetCellAddress():
        return table[table_i-1] + "_" + hwp.KeyIndicator()[-1][1:].split(")")[0]
    
                
#테이블 필드 생성
while True:
        now_tbl = hwp.KeyIndicator()
        hwp.Run("SelectCtrlFront")
        mov_tbl = hwp.KeyIndicator()
        
        if now_tbl == mov_tbl:
                break
        else:
                find_select_table()
                while True:
                        
                        #셀일 경우 주소줄에서 "문자입력"으로 표시되는 부분을 추출
                        cell_state = hwp.KeyIndicator()[-1].split(':')[-1].strip()
                        
                        if cell_state == "문자 입력":
                                #셀 필드명 입력
                                hwp.HAction.GetDefault("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)
                                hwp.HParameterSet.HShapeObject.ShapeTableCell.Editable = 1
                                hwp.HParameterSet.HShapeObject.ShapeTableCell.CellCtrlData.name = GetCellAddress()
                                hwp.HAction.Execute("TablePropertyDialog", hwp.HParameterSet.HShapeObject.HSet)
 
                                #현재 셀 위치 정보 확인
                                cell_now = GetCellAddress()
                                # 오른쪽으로 셀 이동
                                hwp.HAction.Run("TableRightCell")
                                #이동한 셀의 위치 정보 확인
                                cell_move = GetCellAddress()
                                #이동 전의 셀과 이동 후의 셀정보가 같다면 
                                if cell_now == cell_move:
                                        hwp.Run("Cancel")
                                        hwp.Run("SelectCtrlFront")

                                        break
                        else:
                                break