대부분의 HR 시스템에서 인사 프로필 출력기능을 제공하지만, 인사담당자로 일하다 보면 직원들의 프로필을 PPT로 직접 만들어야 할 때가 자주 있습니다. 승진 심사, 해외 주재원 선발, 임원 평가 등 다양한 목적을 위해 인사 프로필을 작성하게 되는데 기본적으로 시스템을 통해 제공되는 양식에는 필요로 하는 정보가 빠져있는 경우가 많기 때문에 어쩔 수 없이 담당자는 노가다의 늪에 빠지게 됩니다.
인사 프로필 작성 뿐만 아니라 다른 영역에서도 엑셀로 데이터를 가지고 있고 정해신 양식대로 엑셀 값을 붙여넣어 PPT 슬라이드를 여러장 만들어야하는 사례가 많이 있을 것 같은데요, 복사 붙여넣기로 이루어진 단순 작업, 더 효율적으로 해치울수 있는 방법이 없을까 고민하셨던 분들을 위해 python-pptx 패키지 튜토리얼을 작성해보았습니다.
실제 인사 데이터를 활용할 수는 없기 때문에 인사 프로필 만들기 대신 인터브랜드에서 매년 발표하는 브랜드 가치 데이터를 활용하여 각 브랜드별 PPT 페이지를 만들어보는 예제를 소개하겠습니다.
1. 필요 패키지 설치
- python-pptx (pip install python-pptx)
- pandas (pip install pandas)
2. 준비물
첨부된 파일을 다운받아 같은 폴더에 저장해주세요.
* 엑셀로 정리된 데이터값과 로고 이미지는 selenium 패키지를 활용하여 Interbrand 홈페이지에서 자동으로 수집하였고, 차트 이미지는 plotly 패키지를 활용하여 자동 생성하였습니다. 향후 포스트에서 웹크롤링과 차트 그리기 등의 내용도 다루어 볼 예정입니다.
3. PPT 템플릿, 엑셀 파일 및 그림파일 읽어오기
#.py 파일을 준비물들과 같은 폴더에 저장했다고 가정하겠습니다.
from pptx import Presentation
prs = Presentation('Best Global Brands 2022_interbrand_template.pptx')
# ppt파일을 읽어와 prs라는 변수명으로 저장합니다.
import pandas as pd
df = pd.read_excel("Best Global Brands 2022_interbrand.xlsx")
# 엑셀파일을 pandas dataframe으로 읽어옵니다.
import os
cwd = os.getcwd()
# cwd(current working directory)를 읽어옵니다.
logos = [file for file in os.listdir(f"{cwd}\logos") if os.path.isfile(f"{cwd}\logos\{file}")]
# logos 폴더 내에 있는 모든 파일명을 logos라는 변수명의 리스트로 저장합니다.
charts = [file for file in os.listdir(f"{cwd}\charts") if os.path.isfile(f"{cwd}\charts\{file}")]
# charts 폴더 내에 있는 모든 파일명을 charts라는 변수명의 리스트로 저장합니다.
check = f"{cwd}\images\check.png"
# images 폴더 내에 있는 체크모양 이미지의 경로값을 check라는 변수명으로 저장합니다.
4. 필요 함수 정의하기
① 원하는 text값을 가진 객체(그림, 도형, 텍스트박스, 표 등)를 읽어오는 함수
def select_shape_by_text(slide, text):
for x in slide.shapes:
# 선택한 슬라이드 내 모든 shape들 중에서
if x.has_text_frame and x.text == text:
# text_frame이 있고 해당 shape의 text값이 입력한 text값과 같으면
return x
# 해당 shape을 반환합니다.
print('요청한 Shape를 찾을 수 없습니다.')
brand_name = select_shape_by_text(slide, 'Brand name')
brand_name.text = 'Hyundai'
prs.save('example.pptx')
위 코드를 실행하면 템플릿 내에서 "Brand name"라는 텍스트를 가진 박스가 선택되고 해당 박스의 텍스트값 Hyundai로 바뀌어 example.pptx라는 파일로 저장되는 것을 확인할 수 있습니다. 이때 기존 템플릿과 달리 폰트가 바뀌게 되는데 폰트를 유지하면서 텍스트만 바꾸는 방법은 뒤에서 소개하겠습니다.
② 원하는 text값을 가진 표를 읽어오는 함수
def select_table_by_text(slide, text):
for x in slide.shapes:
if x.has_table and x.table.cell(0,0).text == text:
return x.table
print('요청한 Shape를 찾을 수 없습니다.')
비슷한 방식으로 정의한 slide 내에서 특정 text값을 가진 표를 읽어오는 함수입니다.
좌측 상단 셀의 text값이 입력한 text값과 같을 경우 해당 표를 읽어옵니다.
③ 선택한 PPT 파일에서 index번째 슬라이드를 복사하여 슬라이드를 만드는 함수
import copy
def copy_slide(prs, index):
template = prs.slides[index]
# prs 내 slide 중 index번째 슬라이드를 선택하여 template라는 변수명으로 저장합니다.
try:
blank_slide_layout = prs.slide_layouts.get_by_name('빈 화면')
except:
blank_slide_layout = prs.slide_layouts[0]
# 새 슬라이드를 만들때 활용할 layout을 선택합니다.
# '빈 화면'이라는 레이아웃을 선택하되 에러가 날 경우 layout중 첫번째를 선택합니다.
copied_slide = prs.slides.add_slide(blank_slide_layout)
# '빈 화면' 레이아웃을 적용한 새 슬라이드를 만들어 copied_slide라는 변수명으로
# 저장합니다.
for shape in template.shapes:
elem = shape.element
new_elem = copy.deepcopy(elem)
copied_slide.shapes._spTree.insert_element_before(new_elem, 'p:extLst')
# template 슬라이드에서 모든 shapes의 element를 복사하여
# copied_slide에 붙여넣습니다.
return copied_slide
# 참고. https://stackoverflow.com/questions/50866634/how-to-copy-a-slide-with-python-pptx
5. 템플릿에 원하는 텍스트 정보 및 이미지 입력하기
① 텍스트박스에 원하는 정보 입력하기
copied_slide = copy_slide(prs, 0)
# 0번째 템플릿 슬라이드를 복사하여 새로운 슬라이드를 만들고
# copied_slide라는 변수명으로 저장합니다.
brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
# 'Brand name'이라는 text를 가진 shape을 찾아서 해당 shape의 text_frame을 읽어옵니다.
p = brand_name.paragraphs[0]
# 선택된 text_frame 내 paragraph중에서 첫번째 문단을 선택하여 읽어옵니다.
run = p.runs[0]
# 선택된 문단의 첫번째 run을 선택하여 읽어옵니다.
run.text = 'Hyundai'
# 선택된 run 의 텍스트값을 원하는 값으로 변환합니다.
위 코드에서 paragraph 오브젝트는 말그대로 텍스트 프레임 내 문단을 의미하기 때문에 바로 이해가 되실텐데요, run의 개념은 생소하실 수 있을 것 같습니다. 같은 문단 내에서 폰트 등 서식이 바뀌게되는 경우 같은 서식을 갖고있는 부분까지가 같은 run에 해당합니다. 한 문단이 모두 같은 서식으로 작성되었다면 문단내 run의 갯수는 하나이며 중간에 서식이 바뀌게되면 run도 나뉘게 됩니다.
② 표에 원하는 정보 입력하기
table = select_table_by_text(copied_slide, 'Rank')
# 먼저 좌측 상단 셀의 텍스트가 Rank인 표를 선택합니다.
rank = table.cell(0,1).text_frame
p = rank.paragraphs[0]
run = p.runs[0]
run.text = '35'
# (0,1) 맨윗줄 왼쪽에서 두번째 셀에 원하는 값을 입력합니다.
③ 이미지 입력하기
from pptx.util import Cm
# 센티미터값 입력을 위한 모듈을 import합니다.
chart = [x for x in charts if 'Hyundai' in x][0]
# charts 폴더 내에 있는 모든 파일명 중에서 Hyundai라는 이름의 파일명을 읽어옵니다.
chart = f"{cwd}\charts\{chart}"
# 해당 파일의 경로를 정의합니다.
copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
# 복사된 슬라이드에 그림을 추가합니다.
# add_picture(파일경로, 가로위치_왼쪽 위 모서리 기준, 세로위치_왼쪽 위 모서리 기준, 넓이, 높이)
# 위치값을 모르겠다면 PPT파일을 열어서 그림을 원하는 위치에 맞추고 우클릭, 크기 및 위치를 눌러
# 확인할 수 있습니다.
④ 읽어온 엑셀파일의 모든 열에 대해서 slide 만들기
for i, r in df.iterrows():
# pandas dataframe의 iterrows함수를 이용해 모든 row를 iterate 합니다.
copied_slide = copy_slide(prs, 0)
brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
p = brand_name.paragraphs[0]
run = p.runs[0]
run.text = r['브랜드명']
comment = select_shape_by_text(copied_slide, 'comment').text_frame
p = comment.paragraphs[0]
run = p.runs[0]
run.text = r['코멘트']
table = select_table_by_text(copied_slide, 'Rank')
rank = table.cell(0,1).text_frame
p = rank.paragraphs[0]
run = p.runs[0]
run.text = str(r['순위'])
value = table.cell(1,1).text_frame
p = value.paragraphs[0]
run = p.runs[0]
run.text = f"{r['브랜드가치']:,} $m"
# 엑셀에는 브랜드가치가 정수값으로 저장되어있기 때문에 ,로 구분한 포맷으로 바꾸어줍니다.
growth = table.cell(2,1).text_frame
p = growth.paragraphs[0]
run = p.runs[0]
run.text = f"{r['성장률']:.0%}"
# 엑셀에는 성장률이 소숫점으로 저장되어있기 때문에 % 포맷으로 바꾸어줍니다.
copied_slide.shapes.add_picture(check, Cm(18.05), Cm(4.05), width=None, height=None)
# 체크모양 이미지를 입력해줍니다.
chart = [x for x in charts if r['브랜드명'] in x][0]
chart = f"{cwd}\charts\{chart}"
copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
# 차트 이미지를 입력해줍니다.
logo = [x for x in logos if r['브랜드명'] in x][0]
logo = f"{cwd}\logos\{logo}"
inserted_logo = copied_slide.shapes.add_picture(logo, Cm(2.38), Cm(4.81))
# 로고 이미지를 입력하여 inserted_logo라는 변수명으로 저장합니다.
# 로고 이미지별로 크기가 제각각이기 때문에 이후 크기를 조정하기 위함입니다.
if inserted_logo.height>Cm(2):
inserted_logo.width = int(inserted_logo.width * (Cm(2)/inserted_logo.height))
inserted_logo.height = Cm(2)
# 높이가 2cm보다 크면 2cm로 줄이고 넓이도 비율에 맞게 줄여줍니다.
if inserted_logo.width>Cm(6):
inserted_logo.height = int(inserted_logo.height * (Cm(6)/inserted_logo.width))
inserted_logo.width = Cm(6)
# 넓이가 6cm보다 크면 6cm로 줄이고 높이도 비율에 맞게 줄여줍니다.
if inserted_logo.height < Cm(2) :
inserted_logo.top = int(Cm(4.81) + (Cm(2)-inserted_logo.height)*0.5)
# 크기 보정 후 높이가 2cm 보다 작아지면 이미지 위치가 위로 쏠리기 때문에
# 아래로 조금 내려줍니다.
prs.save('Best Global Brands 2022_interbrand.pptx')
# 저장해보면 100개의 슬라이드가 생성된것을 확인할 수 있습니다.
6. 전체 코드 정리
앞에서 소개한 코드를 정리하며 오늘의 포스트를 마치겠습니다. 감사합니다.
from pptx import Presentation
from pptx.util import Cm
import pandas as pd
import os
import copy
prs = Presentation('Best Global Brands 2022_interbrand_template.pptx')
slide = prs.slides[0]
df = pd.read_excel("Best Global Brands 2022_interbrand.xlsx")
cwd = os.getcwd()
logos = [file for file in os.listdir(f"{cwd}\logos") if os.path.isfile(f"{cwd}\logos\{file}")]
charts = [file for file in os.listdir(f"{cwd}\charts") if os.path.isfile(f"{cwd}\charts\{file}")]
check = f"{cwd}\images\check.png"
def select_shape_by_text(slide, text):
for x in slide.shapes:
if x.has_text_frame and x.text == text:
return x
print('요청한 Shape를 찾을 수 없습니다.')
def select_table_by_text(slide, text):
for x in slide.shapes:
if x.has_table and x.table.cell(0,0).text == text:
return x.table
print('요청한 Shape를 찾을 수 없습니다.')
def copy_slide(prs, index):
template = prs.slides[index]
try:
blank_slide_layout = prs.slide_layouts.get_by_name('빈 화면')
except:
blank_slide_layout = prs.slide_layouts[0]
copied_slide = prs.slides.add_slide(blank_slide_layout)
for shape in template.shapes:
elem = shape.element
new_elem = copy.deepcopy(elem)
copied_slide.shapes._spTree.insert_element_before(new_elem, 'p:extLst')
return copied_slide
for i, r in df.iterrows():
copied_slide = copy_slide(prs, 0)
brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
p = brand_name.paragraphs[0]
run = p.runs[0]
run.text = r['브랜드명']
comment = select_shape_by_text(copied_slide, 'comment').text_frame
p = comment.paragraphs[0]
run = p.runs[0]
run.text = r['코멘트']
table = select_table_by_text(copied_slide, 'Rank')
rank = table.cell(0,1).text_frame
p = rank.paragraphs[0]
run = p.runs[0]
run.text = str(r['순위'])
value = table.cell(1,1).text_frame
p = value.paragraphs[0]
run = p.runs[0]
run.text = f"{r['브랜드가치']:,} $m"
growth = table.cell(2,1).text_frame
p = growth.paragraphs[0]
run = p.runs[0]
run.text = f"{r['성장률']:.0%}"
copied_slide.shapes.add_picture(check, Cm(18.05), Cm(4.05), width=None, height=None)
chart = [x for x in charts if r['브랜드명'] in x][0]
chart = f"{cwd}\charts\{chart}"
copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
logo = [x for x in logos if r['브랜드명'] in x][0]
logo = f"{cwd}\logos\{logo}"
inserted_logo = copied_slide.shapes.add_picture(logo, Cm(2.38), Cm(4.81))
if inserted_logo.height>Cm(2):
inserted_logo.width = int(inserted_logo.width * (Cm(2)/inserted_logo.height))
inserted_logo.height = Cm(2)
if inserted_logo.width>Cm(6):
inserted_logo.height = int(inserted_logo.height * (Cm(6)/inserted_logo.width))
inserted_logo.width = Cm(6)
if inserted_logo.height < Cm(2) :
inserted_logo.top = int(Cm(4.81) + (Cm(2)-inserted_logo.height)*0.5)
prs.save('Best Global Brands 2022_interbrand.pptx')
추가. 연도별 브랜드가치 차트를 ppt에서 직접 그리는 방법은 아래 게시글을 참고해주세요.
'코딩' 카테고리의 다른 글
OPENDART API를 활용하여 파이썬으로 기업 정보 검색하기 (0) | 2023.08.19 |
---|---|
파이썬 Chat GPT로 인사 평가 의견을 자동 요약하기 (1) | 2023.06.27 |
윈도우에서 파이썬으로 아웃룩 이메일 자동화하기 (0) | 2023.06.07 |
회사는 직원들의 ChatGPT사용을 규제해야 할까요? (0) | 2023.06.05 |
파이썬으로 Chat GPT에게 외부 정보 학습시키기 (3) | 2023.05.25 |