교무업무자동화

[구글폼 자동화] 학년 초 기초생활조사5 #한글파일 자동화

dinggle 2024. 2. 22. 17:23

 

 

[구글폼 자동화] 학년 초 기초생활조사4 #구글시트 생성

[구글폼 자동화] 학년초 학생 기초생활조사3 #구글폼 생성 [구글폼 자동화] 학년초 학생 기초상담카드2 #필드이름 [구글폼 자동화] 학년초 학생 기초상담카드 #한글양식 [구글폼 자동화] 학년초

dingglego.tistory.com

 

구글시트(엑셀)를 기반으로 한글파일 자동화 작업하기

< 목차 >
1. 한글파일 자동화 작업 순서
2. 주요 프로세스 파이썬 코드 설명
3. 파이썬 전체 코드

 

한글파일 자동화 작업의 순서

 

이번 프로젝트에서 구현한 것은 구글폼으로 학생 기초생활조사 자료를 취합하고, 재가공과 상담활동이 쉽도록 한글파일의 양식에 채워넣는 것이다.

 

한글양식파일로 만들면 재가공과 수정이 쉽고, PDF로 변환하여 태블릿으로 불러와 상담 중에 필요한 내용을 수기로 적어 넣을 수도 있기 때문이다. 

 

이러한 작업을 위해 파이썬으로 구현한 프로세스는 다음과 같다. 

 

데이터 프레임 생성

 

구글폼으로 학생들에게 자료를 입력받아 생성한 구글시트를 엑셀파일로 로컬컴퓨터에 저정한다.

물론 구글시트에 입력된 자료를 그대로 받아와 데이터 프레임을 생성할 수 있지만 별도의 설정이 필요하기 때문에 간편하게 엑셀파일로 다운 받는 것으로 대체했다. 이 엑셀 파일을 바탕으로 데이터프레임을 생성한다. 

 

문서양식 복사

 

한글파일 양식에는 기본적으로 1페이지에 기본 양식이 1개만 있기 때문에 메일머지처럼 작업하기 위해서 데이터프레임에서 학생의 수를 읽어오고, 이 수만큼 양식을 복사한다. 

 

문서양식 채우기

 

학생의 수만큼 생성된 문서 양식에 데이터프레임의 각 row를 순회하면서 원하는 셀에 하나씩 데이터를 입력한다. 

 

 

 

데이터를 분석하거나, 가공하는 과정이 없고 단순히 셀에 원하는 값을 채워 넣는 것이기 때문에 코드가 복잡하지도 않고, 단순한 편이다. 그리고 한글 양식에서 셀 필드이름을 기반으로 코드가 작성되었기 때문에 양식이 변경된다면 코드도 변경해야 한다.

 

 

주요 프로세스 파이썬 코드 설명

1. 라이브러리 임포트

import win32com.client as win32
import os
import tkinter as tk
from tkinter import filedialog
import shutil
import pandas as pd
import warnings
from datetime import datetime

 

필요한 라이브러리들을 임포트합니다. 이 코드는 후에 COM 객체 조작을 위해 win32com.client, 파일 시스템 조작을 위해 osshutil, GUI 생성을 위해 tkinter, 엑셀 파일 처리를 위해 pandas를 사용합니다. warningsdatetime은 각각 경고 제어와 시간 처리를 위해 임포트합니다.

 

2. 엑셀파일에서 데이터프레임 생성 및 가공

root = tk.Tk()
root.withdraw()
xls_path = filedialog.askopenfilename(title="구글시트에 다운로드한 엑셀파일을 선택하세요", filetypes=[("xlsx files", "*.xlsx")])
root.destroy()

df = pd.read_excel(xls_path)
df = df.drop(df.columns[[0,1]], axis=1)
df_cnt = len(df)
df = df.fillna("")
  • pandas를 사용하여 선택된 엑셀 파일을 읽어 데이터프레임으로 변환합니다.
  • 데이터프레임에서 첫 두 열을 삭제하고 빈 값은 빈 문자열로 채웁니다.
  • 데이터프레임의 행 수를 구하여 df_cnt 변수에 저장합니다.

3. 한글파일 선택 및 문서양식 복사

# # 한글파일
root = tk.Tk() #tk 개체 생성
root.withdraw() #tkinter 창을 숨김
path = filedialog.askopenfilename(title="작업할 한글파일을 선택하세요", filetypes=[("hwp files", "*.hwp")])

current_time = datetime.today().strftime('%Y-%m-%d')
file_name = f"학생기초생활조사서({current_time}).hwp"
dir_path = os.path.dirname(path) #파일경로 추출
new_filename = os.path.join(dir_path, file_name) #new_파일이름 으로 지정

root.destroy() #작업창 닫기

# #문서양식 복사

hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
shutil.copyfile(path, new_filename)
hwp.Open(new_filename)
hwp.XHwpWindows.Item(0).Visible = True

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

field_list = [i for i in hwp.GetFieldList().split("\x02")]
    
for i in range(df_cnt - 1):
    hwp.Run("CopyPage") #쪽 복사
    hwp.Run("PastePage") #쪽 붙여넣기

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

 

  • 현재 날짜를 기반으로 한 새로운 파일 이름을 생성합니다. 이를 통해 파일 이름에 날짜가 포함되도록 합니다.
  • path에서 디렉토리 경로를 추출하고, 새 파일 이름과 결합하여 새로운 파일의 경로를 생성합니다.
  • 문서의 페이지를 복사하여 붙여넣기합니다. 이전에 엑셀에서 읽어온 데이터 수에 따라 페이지를 복사합니다. 

 

4. 한글양식에 값 입력하기

# ## 양식 채우기

#입력할 필드목록 생성
dic_Field = {'학번':'1_B1', '성명':'1_D1', '핸드폰':'1_F1',
             '가족관계1_관계':'1_B3','가족관계1_성명':'1_C3','가족관계1_연락처':'1_D3','가족관계1_동거여부':'1_E3','가족관계1_기타':'1_F3',
             '가족관계2_관계':'1_B4', '가족관계2_성명':'1_C4', '가족관계2_연락처':'1_D4','가족관계2_동거여부':'1_E4', '가족관계2_기타':'1_F4', 
             
             '대입특별전형 해당사항_기초':'1_B6','대입특별전형 해당사항_다문화':'1_C6','대입특별전형 해당사항_다자녀':'1_D6','대입특별전형 해당사항_국가보훈':'1_E6',
             '대입특별전형 해당사항_기타':'1_F6',
             
             '건강상태':'1_B7',
             '진로 희망 분야':'3_A2', '1순위':'3_C2', '2순위':'3_E2','3순위':'3_C3',
             '4순위':'3_E3', '5순위':'3_C4', '6순위':'3_E4',
             
             '임원활동 실적':'4_C1', '희망 학급 역할':'4_C2', '기숙사_입사':'4_C3', '기숙사_입사 희망':'4_F3', '기숙사_해당 없음':'4_I3',
             
             '과목_월':'4_C5', '과목_화':'4_D5', '과목_수':'4_E5', '과목_목':'4_G5', '과목_금':'4_H5','과목_토':'4_J5', '과목_일':'4_K5',
             '시간_월':'4_C6', '시간_화':'4_D6', '시간_수':'4_E6', '시간_목':'4_G6', '시간_금':'4_H6','시간_토':'4_J6', '시간_일':'4_K6', 
             '기타사항':'5_A1'
}

for i in range(df_cnt):
    #학번
    hwp.PutFieldText(dic_Field['학번']+f'{{{{{i}}}}}', df['학번'][i])
    #성명
    hwp.PutFieldText(dic_Field['성명']+f'{{{{{i}}}}}', df['성명'][i])
    #핸드폰
    hwp.PutFieldText(dic_Field['핸드폰']+f'{{{{{i}}}}}', df['핸드폰'][i])

    #가족관계
    family_relations = [('부', '2-1. 부 성명', '2-2. 부 연락처', '가족관계1'), 
                        ('모', '2-3. 모 성명', '2-4. 모 연락처', '가족관계2')]
    
    for relations, name, contact, family in family_relations:
        if df[name][i]:
            hwp.PutFieldText(dic_Field[f'{family}_관계']+f'{{{{{i}}}}}', relations)
            hwp.PutFieldText(dic_Field[f'{family}_성명']+f'{{{{{i}}}}}', df[f'{name}'][i])
            hwp.PutFieldText(dic_Field[f'{family}_연락처']+f'{{{{{i}}}}}', df[f'{contact}'][i])
            if relations in df['2-5. 동거 여부'][i]:
                hwp.PutFieldText(dic_Field[f'{family}_동거여부']+f'{{{{{i}}}}}', "○")

    #대입특별전형
    special_category = ['기초', '다문화', '다자녀', '국가보훈']
    
    found_category = False
    
    for category_item in special_category:
        if category_item in df['3. 대입 특별 전형 해당 사항'][i]:
            hwp.PutFieldText(dic_Field[f'대입특별전형 해당사항_{category_item}']+f'{{{{{i}}}}}', "○")
            found_category = True
    if not found_category:
        hwp.PutFieldText(dic_Field['대입특별전형 해당사항_기타']+f'{{{{{i}}}}}',df['3. 대입 특별 전형 해당 사항'][i])
    if df['3. 대입 특별 전형 해당 사항'][i] == "해당없음":
        hwp.PutFieldText(dic_Field['대입특별전형 해당사항_기타']+f'{{{{{i}}}}}',"")

    #건강상태 및 특이사항    
    hwp.PutFieldText(dic_Field['건강상태']+f'{{{{{i}}}}}', df['4. 건강 상태 및 특이 사항'][i])

    #진로희망분야
    hwp.PutFieldText(dic_Field['진로 희망 분야']+f'{{{{{i}}}}}', df['진로 희망 분야'][i])            
    
    #희망대학, 학과
    for idx in range(1,7):
        hwp.PutFieldText(dic_Field[f'{idx}순위']+f'{{{{{i}}}}}', df[f'{idx}순위'][i])

    #임원활동 실적        
    hwp.PutFieldText(dic_Field['임원활동 실적']+f'{{{{{i}}}}}', df['학생회, 학급 임원 활동 실적'][i])
    #희망학급 역할
    hwp.PutFieldText(dic_Field['희망 학급 역할']+f'{{{{{i}}}}}', df['희망 학급 역할'][i])
    
    #기숙사 여부
    dormitory = df['기숙사'][i]
    if dormitory == "입사":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "☑ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "□ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "□ 해당 없음")

    elif dormitory == "입사 희망":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "□ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "☑ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "□ 해당 없음")
        
    elif dormitory == "해당 없음":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "□ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "□ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "☑ 해당 없음")

    #학원 수강        
    weekdays = ['월', '화', '수', '목', '금','토']
    
    for weekday in weekdays:
        #수강과목 출력
        hwp.PutFieldText(dic_Field[f'과목_{weekday}']+f'{{{{{i}}}}}', df[f'학원 수강 과목 [{weekday}]'][i])
        #수강시간 간단 출력
        time_str = df[f'학원 수강 시간 [{weekday}]'][i]
        num_list = time_str.replace("시", "")
        time_list = num_list.split(",")

        if len(time_list) > 1:
            simple_str = f"{time_list[0]}시 ~{time_list[-1]}시"
        elif len(time_list) == 1:
            simple_str = time_str

        hwp.PutFieldText(dic_Field[f'시간_{weekday}']+f'{{{{{i}}}}}', simple_str)

    hwp.PutFieldText(dic_Field['기타사항']+f'{{{{{i}}}}}', df['기타 사항'][i])

hwp.Save()

 

dic_Field에 입력할 필드 목록을 생성한다. 딕셔너리 형태로 저장하였고, key는 입력하는 내용, value에는 필드이름을 입력하였다. 

 

for문을 통해 데이터프레임의 각 row를 순회한다. 학번을 입력하는 코드를 살펴보면, 

hwp.PutFieldText(dic_Field['학번']+f'{{{{{i}}}}}', df['학번'][i])

 

셀에 원하는 값을 입력하는 것은 PutFieldText를 이용한다.

PutFieldText(필드이름, 값)의 형태로 속성을 입력하면 된다. 이때 {{{{{i}}}}} 의 의미는 한글파일에 동일한 필드이름이 여러개 있을 때 i번째 필드에 값을 입력하라는 의미이다. f-스트링을 이용해서 i값을 매칭해주면 순차적으로 i번째에 해당하는 필드에 i번째 값이 입력되는 원리이다. 

 

가족관계 항목과 대입특별전형과 같이 하나의 항목에 여러개의 값이 있는 경우는 리스트를 통해 입력할 항목을 묶어 주고, if문을 통해 상황에 따라 적합한 값을 입력하도록 한다. 

 

학원 수강 과목과 요일을 입력하는 부분이 조금 복잡해 보이는데 구글시트에서 입력한 값을 살펴보면 체크박스 값이 나열되어 입력된다. 이것을 7시~10시와 같은 형태로 심플한 형태로 바꿔주는 코드이다. 

 

아래 코드에서 해당 요일에 대한 학원 수강 시간 정보를 가져와서 시간 형식에서 "시" 문자를 제거하고, 시간을 리스트로 분할한다. 

time_str = df[f'학원 수강 시간 [{weekday}]'][i]
num_list = time_str.replace("시", "")
time_list = num_list.split(",")

 

그리고 수강 시간이 여러 시간대인 경우 시작 시간부터 종료 시간까지의 시간을 형식화하여 simple_str 변수에 저장하고, 한 시간대만 선택한 경우 time_str을 그대로 사용하여 출력하도록 합니다.

if len(time_list) > 1:
    simple_str = f"{time_list[0]}시 ~{time_list[-1]}시"
elif len(time_list) == 1:
    simple_str = time_str

 

 

파이썬 전체코드

import win32com.client as win32
import os
import tkinter as tk
from tkinter import filedialog
import shutil
import pandas as pd
import warnings
from datetime import datetime

# # 엑셀

#오류 경고 무시 / 기본스타일 없음
warnings.filterwarnings(action='ignore')

root = tk.Tk() #tk 개체 생성
root.withdraw() #tkinter 창을 숨김
xls_path = filedialog.askopenfilename(title="구글시트에 다운로드한 엑셀파일을 선택하세요", filetypes=[("xlsx files", "*.xlsx")])
root.destroy() #작업창 닫기


# #데이터프래임 생성

df = pd.read_excel(xls_path)
df = df.drop(df.columns[[0,1]], axis=1) #불필요한 열 삭제

df_cnt = len(df) #입력된 학생수 카운팅

df = df.fillna("") #NaN 값을 대체

# # 한글파일

root = tk.Tk() #tk 개체 생성
root.withdraw() #tkinter 창을 숨김
path = filedialog.askopenfilename(title="작업할 한글파일을 선택하세요", filetypes=[("hwp files", "*.hwp")])

current_time = datetime.today().strftime('%Y-%m-%d')
file_name = f"학생기초생활조사서({current_time}).hwp"
dir_path = os.path.dirname(path) #파일경로 추출
new_filename = os.path.join(dir_path, file_name) #new_파일이름 으로 지정

root.destroy() #작업창 닫기

# #문서양식 복사

hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
shutil.copyfile(path, new_filename)
hwp.Open(new_filename)
hwp.XHwpWindows.Item(0).Visible = True

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

field_list = [i for i in hwp.GetFieldList().split("\x02")]
    
for i in range(df_cnt - 1):
    hwp.Run("CopyPage") #쪽 복사
    hwp.Run("PastePage") #쪽 붙여넣기

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

# ## 양식 채우기

#입력할 필드목록 생성
dic_Field = {'학번':'1_B1', '성명':'1_D1', '핸드폰':'1_F1',
             '가족관계1_관계':'1_B3','가족관계1_성명':'1_C3','가족관계1_연락처':'1_D3','가족관계1_동거여부':'1_E3','가족관계1_기타':'1_F3',
             '가족관계2_관계':'1_B4', '가족관계2_성명':'1_C4', '가족관계2_연락처':'1_D4','가족관계2_동거여부':'1_E4', '가족관계2_기타':'1_F4', 
             
             '대입특별전형 해당사항_기초':'1_B6','대입특별전형 해당사항_다문화':'1_C6','대입특별전형 해당사항_다자녀':'1_D6','대입특별전형 해당사항_국가보훈':'1_E6',
             '대입특별전형 해당사항_기타':'1_F6',
             
             '건강상태':'1_B7',
             '진로 희망 분야':'3_A2', '1순위':'3_C2', '2순위':'3_E2','3순위':'3_C3',
             '4순위':'3_E3', '5순위':'3_C4', '6순위':'3_E4',
             
             '임원활동 실적':'4_C1', '희망 학급 역할':'4_C2', '기숙사_입사':'4_C3', '기숙사_입사 희망':'4_F3', '기숙사_해당 없음':'4_I3',
             
             '과목_월':'4_C5', '과목_화':'4_D5', '과목_수':'4_E5', '과목_목':'4_G5', '과목_금':'4_H5','과목_토':'4_J5', '과목_일':'4_K5',
             '시간_월':'4_C6', '시간_화':'4_D6', '시간_수':'4_E6', '시간_목':'4_G6', '시간_금':'4_H6','시간_토':'4_J6', '시간_일':'4_K6', 
             '기타사항':'5_A1'
}

for i in range(df_cnt):
    #학번
    hwp.PutFieldText(dic_Field['학번']+f'{{{{{i}}}}}', df['학번'][i])
    #성명
    hwp.PutFieldText(dic_Field['성명']+f'{{{{{i}}}}}', df['성명'][i])
    #핸드폰
    hwp.PutFieldText(dic_Field['핸드폰']+f'{{{{{i}}}}}', df['핸드폰'][i])

    #가족관계
    family_relations = [('부', '2-1. 부 성명', '2-2. 부 연락처', '가족관계1'), 
                        ('모', '2-3. 모 성명', '2-4. 모 연락처', '가족관계2')]
    
    for relations, name, contact, family in family_relations:
        if df[name][i]:
            hwp.PutFieldText(dic_Field[f'{family}_관계']+f'{{{{{i}}}}}', relations)
            hwp.PutFieldText(dic_Field[f'{family}_성명']+f'{{{{{i}}}}}', df[f'{name}'][i])
            hwp.PutFieldText(dic_Field[f'{family}_연락처']+f'{{{{{i}}}}}', df[f'{contact}'][i])
            if relations in df['2-5. 동거 여부'][i]:
                hwp.PutFieldText(dic_Field[f'{family}_동거여부']+f'{{{{{i}}}}}', "○")

    #대입특별전형
    special_category = ['기초', '다문화', '다자녀', '국가보훈']
    
    found_category = False
    
    for category_item in special_category:
        if category_item in df['3. 대입 특별 전형 해당 사항'][i]:
            hwp.PutFieldText(dic_Field[f'대입특별전형 해당사항_{category_item}']+f'{{{{{i}}}}}', "○")
            found_category = True
    if not found_category:
        hwp.PutFieldText(dic_Field['대입특별전형 해당사항_기타']+f'{{{{{i}}}}}',df['3. 대입 특별 전형 해당 사항'][i])
    if df['3. 대입 특별 전형 해당 사항'][i] == "해당없음":
        hwp.PutFieldText(dic_Field['대입특별전형 해당사항_기타']+f'{{{{{i}}}}}',"")

    #건강상태 및 특이사항    
    hwp.PutFieldText(dic_Field['건강상태']+f'{{{{{i}}}}}', df['4. 건강 상태 및 특이 사항'][i])

    #진로희망분야
    hwp.PutFieldText(dic_Field['진로 희망 분야']+f'{{{{{i}}}}}', df['진로 희망 분야'][i])            
    
    #희망대학, 학과
    for idx in range(1,7):
        hwp.PutFieldText(dic_Field[f'{idx}순위']+f'{{{{{i}}}}}', df[f'{idx}순위'][i])

    #임원활동 실적        
    hwp.PutFieldText(dic_Field['임원활동 실적']+f'{{{{{i}}}}}', df['학생회, 학급 임원 활동 실적'][i])
    #희망학급 역할
    hwp.PutFieldText(dic_Field['희망 학급 역할']+f'{{{{{i}}}}}', df['희망 학급 역할'][i])
    
    #기숙사 여부
    dormitory = df['기숙사'][i]
    if dormitory == "입사":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "☑ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "□ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "□ 해당 없음")

    elif dormitory == "입사 희망":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "□ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "☑ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "□ 해당 없음")
        
    elif dormitory == "해당 없음":
        hwp.PutFieldText(dic_Field['기숙사_입사']+f'{{{{{i}}}}}', "□ 입사")
        hwp.PutFieldText(dic_Field['기숙사_입사 희망']+f'{{{{{i}}}}}', "□ 입사 희망")
        hwp.PutFieldText(dic_Field['기숙사_해당 없음']+f'{{{{{i}}}}}', "☑ 해당 없음")

    #학원 수강        
    weekdays = ['월', '화', '수', '목', '금','토']
    
    for weekday in weekdays:
        #수강과목 출력
        hwp.PutFieldText(dic_Field[f'과목_{weekday}']+f'{{{{{i}}}}}', df[f'학원 수강 과목 [{weekday}]'][i])
        #수강시간 간단 출력
        time_str = df[f'학원 수강 시간 [{weekday}]'][i]
        num_list = time_str.replace("시", "")
        time_list = num_list.split(",")

        if len(time_list) > 1:
            simple_str = f"{time_list[0]}시 ~{time_list[-1]}시"
        elif len(time_list) == 1:
            simple_str = time_str

        hwp.PutFieldText(dic_Field[f'시간_{weekday}']+f'{{{{{i}}}}}', simple_str)

    hwp.PutFieldText(dic_Field['기타사항']+f'{{{{{i}}}}}', df['기타 사항'][i])

hwp.Save()