본문 바로가기
아래아한글 자동화/python+hwp 중급

[QnA] A문서 스타일을 B문서에 적용하는 가장 간편한 코딩

by 일코 2022. 12. 4.

 

 

더브릴레 가벼운 남자 여자 블루라이트차단 안경

COUPANG

www.coupang.com

똑똑해 보이는 안경이네요! 여친 선물해주시면 좋겠어요^^
(이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.)

 

 

안녕하세요. 한글자동화를 이용해 문서의 서식을 바꾸는 코드를 만들고 싶어 문의드립니다.
회사에서 보고서 작업을 하는데 동일한 문서에서 서식을 다르게 하여 수정 후 내용을 첨가하여 제출하는 경우가 많습니다.
"서식 유지한 채로 스타일을 모두 "바탕글"로 바꾸는 코딩" 에서 일부 수정하면 될 것 같은데
"스타일_조회"함수에서 "Act = hwp.CreateAction("Style")"에서 object가 추출되지 않아서 Nonetype object로 되고 그로 인해 "Set = Act.CreateSet()" 부분이 작동하지 않아 오류가 발생합니다.
그래서 "hwp" 자식 클래스를 "dir" 함수로 살펴보는데 "CreateAction"는 있으나 그 하부에는 "Style"이라는 자식 클래스가 없습니다. 설치 과정 중에 잘못된건지 몰라서 질문을 남깁니다.
1. 다음과 같은 경우에 어떻게 해결할 수 있는지 궁금합니다.
2. A라는 문서의 서식스타일에 맞춰 B라는 문서의 서식스타일을 바꾸는 코드를 만드려고 하는데 혹시 괜찮은 방법이 있는지 여쭤봅니다.

한/글에서 제공하고 있는 기본 기능 중에, "문서 끼워넣기(Ctrl-O)"라는 명령이 있습니다.

문서 끼워넣기의 가장 큰 장점은 글자, 문단, 쪽 모양 및 스타일을 각각 유지할지 선택할 수 있다는 점입니다.

사용방법을 먼저 보여드리겠습니다.

아래와 같은 탬플릿문서 A가 있다고 가정해보겠습니다.

기본 스타일에서 서체 크기만 조정된 상태입니다. 개요1부터 3까지 각각 25, 18, 14p의 크기가 적용되어 있습니다.

변경하고자 하는 문서B는 아래와 같습니다.

기본스타일로 작성된 문서라서 서체 크기가 모두 10포인트입니다.

사용방법은 아래와 같습니다.

1. 문서A의 모든 내용을 지우고

2. B문서를 끼워넣기합니다. 이 때 서식유지옵션을 모두 해제합니다.

탬플릿인 문서A의 스타일이 적용되었습니다.

이 상태로 "다른이름으로 저장" 해 주시면 되겠네요.

이 작업도 파이썬 코딩으로 가능할까요? (물론 가능합니다.)

코딩하는 과정을 한 번 보여드리겠습니다.

API문서를 참고하면서 작성하는 방법도 가능하겠지만

이렇게 한/글에서 제공하는 기능은 매크로 녹화를 통해 코드를 불러오는 편이 훨씬 쉽고 간편합니다.

 

1. 매크로 녹화를 실행합니다. (Shift-Alt-H)

 

2. 전체 선택(Ctrl-A) 후, 본문 내용을 모두 삭제(Del)합니다.

문서 마지막에 빈 줄이 없는 경우 글머리가 남으므로, Backspace를 추가로 눌러줘야 합니다.

 

3. 이제 끼워넣기(Ctrl-O) 명령을 실행해 문서B(변경할문서.hwp)를 불러옵니다.

 

4. 다른이름(변경한문서.hwp)으로 저장합니다.

 

5. 이제 코딩에 필요한 모든 과정의 녹화를 마쳤습니다.

Shift-Alt-X를 눌러 녹화를 종료하고,

Shift-Alt-L을 눌러 매크로목록을 연 후

녹화된 매크로를 선택하고 코드 편집(Alt-C)을 실행합니다.

 

위 과정을 통해 추출한 매크로는 아래와 같습니다.

한/글의 스크립트매크로가 익숙하지 않은 분들을 위해 주석을 붙여두었습니다.

function OnScriptMacro_중국어1성()
{
	HAction.Run("SelectAll");  // 전체선택
	HAction.Run("DeleteBack");  // 백스페이스
	HAction.Run("DeleteBack");  //  백스페이스
	HAction.GetDefault("InsertFile", HParameterSet.HInsertFile.HSet);  // 끼워넣기 시작
	with (HParameterSet.HInsertFile)  // 끼워넣기 파라미터 입력
	{
		FileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경할문서.hwp";  // 끼워넣을 문서
		KeepSection = 0;  // 쪽모양 유지여부(0:버림, 1:유지)
		KeepCharshape = 0;  // 글자모양 유지여부
		KeepParashape = 0;  // 문단모양 유지여부
		KeepStyle = 0;  // 스타일 유지여부(스타일을 버리면 탬플릿의 스타일이 적용됨)
	}
	HAction.Execute("InsertFile", HParameterSet.HInsertFile.HSet);  // 위 파라미터로 끼워넣기 실행
	
    HAction.GetDefault("PictureSaveAsAll", HParameterSet.HSaveAsImage.HSet);  // 문서를 그림으로 저장(미리보기용)
	with (HParameterSet.HSaveAsImage)  // 파라미터 없음
	{
	}
	HAction.Execute("PictureSaveAsAll", HParameterSet.HSaveAsImage.HSet);  // 문서를 그림으로 저장 실행
	HAction.GetDefault("FileSaveAs_S", HParameterSet.HFileOpenSave.HSet);  // 다른이름으로 저장 실행
	with (HParameterSet.HFileOpenSave)  // 다른이름으로 저장 파라미터 입력
	{
		FileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경한문서.hwp";  // 저장할 파일명
		Format = "HWP";  // 저장할 포맷(Native HWP)
	}
	HAction.Execute("FileSaveAs_S", HParameterSet.HFileOpenSave.HSet);  // 다른이름으로 저장 실행
}

위 스크립트 중에 PictureSaveAsAll은 내부적으로 미리보기 이미지를 만드는 과정이므로 생략해도 됩니다.

나머지 코드를 파이썬 코드로 변환합니다.

변환 과정을 움짤로 보여드리겠습니다.

저는 파이참 유저라서 파이참에 붙여넣은 후 교정했습니다.

1. 찾아바꾸기로 세미콜론을 모두 삭제했습니다.

2. HAction은 hwp.HAction으로, HParameterSet은 hwp.HParameterSet으로 네임스페이스를 지정해줍니다.

네임스페이스를 지정하는 대신 아래처럼 모든 HAction 과 HParameterSet 및 파라미터 앞에 "hwp."을 직접 삽입하는 방법도 있습니다.

이어서 불필요한 중괄호와 (JScript의) with구문도 삭제해줍니다.

 

아직 한 가지 과정이 남아있습니다. 파라미터셋 인자들의 대소문자 여부를 직접 체크해야 합니다.

한/글의 스크립트는 대소문자를 구분하지 않는 반면 파이썬에서 대소문자를 구분하기 때문에 발생하는 과정입니다.

일일이 소스코드를 확인하며 대소문자를 체크하는 것은 굉장히 번거로운 일이므로

우선 코드를 완성해서 실행해보면서, 친절한 오류메시지를 보고 수정해 나가면 됩니다.

아래처럼 코드 일부를 함수로 묶고, 구조화해보았습니다. 코드 내용은 거의 동일합니다.

import os
from tkinter.filedialog import askopenfilename

import win32com.client as win32


def 한글_시작():
    hwp_app = win32.gencache.EnsureDispatch("hwpframe.hwpobject")  # 한/글 백그라운드에서 실행
    hwp_app.XHwpWindows.Item(0).Visible = True  # 백그라운드 해제
    hwp_app.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")  # 보안모듈 실행
    hwp_app.Run("FileOpen")  # 문서 열기 다이얼로그 실행
    return hwp_app  # 실행된 한/글 오브젝트 리턴


def 전체삭제():
    hwp.HAction.Run("SelectAll")
    hwp.HAction.Run("DeleteBack")
    hwp.HAction.Run("DeleteBack")


def 문서삽입(문서경로):
    hwp.HAction.GetDefault("InsertFile", hwp.HParameterSet.HInsertFile.HSet)
    # hwp.HParameterSet.HInsertFileFileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경할문서.hwp"
    hwp.HParameterSet.HInsertFile.FileName = 문서경로
    hwp.HParameterSet.HInsertFile.KeepSection = 0
    hwp.HParameterSet.HInsertFile.KeepCharshape = 0
    hwp.HParameterSet.HInsertFile.KeepParashape = 0
    hwp.HParameterSet.HInsertFile.KeepStyle = 0
    hwp.HAction.Execute("InsertFile", hwp.HParameterSet.HInsertFile.HSet)


def 다른이름으로저장(저장경로):
    hwp.HAction.GetDefault("FileSaveAs_S", hwp.HParameterSet.HFileOpenSave.HSet)
    # hwp.HParameterSet.HFileOpenSave.FileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경한문서.hwp"
    hwp.HParameterSet.HFileOpenSave.FileName = 저장경로
    hwp.HParameterSet.HFileOpenSave.Format = "HWP"
    hwp.HAction.Execute("FileSaveAs_S", hwp.HParameterSet.HFileOpenSave.HSet)


def 바꿀문서선택():
    문서경로 = askopenfilename(title="서식을 변경할 문서를 선택해주세요.",
                           initialdir=os.getcwd(), filetypes=[("한/글 문서", "*.hwp *.hwpx")])
    return 문서경로


def 한글종료():
    hwp.Clear(option=1)  # 1: 변경내용 버림
    hwp.Quit()
    

if __name__ == '__main__':
    hwp = 한글_시작()  # 탬플릿문서 선택
    전체삭제()  # 탬플릿문서 본문 전부 삭제
    바꿀문서경로 = 바꿀문서선택()  # tk 파일선택 다이얼로그창 실행하여 바꿀 문서 선택
    문서삽입(바꿀문서경로)  # 끼워넣기 실행
    다른이름으로저장(바꿀문서경로)
    한글종료()

 

위 코드로 한 번 실행해보겠습니다. (오류가 발생할 예정입니다.)

어? 오류가 발생했습니다.

로그를 읽어보니 hwp.HParameterSet.HInsertFile  안에 FileName 이라는 속성이 없다고 나옵니다.

저도 예전엔 이런 경우에 굉장히 답답했는데, 하도 여러 번 경험하다 보니 이젠 다소 익숙해졌습니다.

올바른 인자명을 찾는 방법은 여러 가지가 있지만, 가장 간편한 방법은

pythonwin.exe로 생성한 7d2b6f3c-1d95-4e0c-bf5a-5ee564186fbcx0x1x0.py 파일을 열어서

직접 HInsertFile 클래스를 탐색하는 것입니다.

인자 확인방법 및 pythonwin 사용법은 관련글 링크를 남겨두겠습니다.

한/글 메서드별 인자 확인하는 방법(feat. ipython) (martinii.fun)

 

한/글 메서드별 인자 확인하는 방법(feat. ipython)

FileName 과 filename 사이에서 고민하고 있는 "회사원코딩"의 수강생입니다. ^^; 다음 내용을 좀 더 상세히 알 수 있을까요? 어떻게 docstring을 통해 대소문자 점검을 할 수 있는지? 미리 어떤 인자가 파

martinii.fun

 

하여튼 7d2b6f3c~를 열어서 HInsertFile을 검색해보면 아래와 같은 클래스 정의가 뜹니다.

class HInsertFile(DispatchBaseClass):
    'HInsertFile Interface'
    CLSID = IID('{39CD2236-B0B5-4BAF-82E2-BC38C8E51A7E}')
    coclass_clsid = None

    def CreateItemArray(self, wszItemName=defaultNamedNotOptArg, pVal=defaultNamedNotOptArg):
        'method CreateItemArray'
        return self._oleobj_.InvokeTypes(15000, LCID, 1, (24, 0), ((8, 1), (3, 1)), wszItemName
                                         , pVal)

    _prop_map_get_ = {
        "FileArg": (28705, 2, (8, 0), (), "FileArg", None),
        "FileFormat": (28704, 2, (8, 0), (), "FileFormat", None),
        "HSet": (1, 2, (9, 0), (), "HSet", None),
        "KeepCharshape": (28710, 2, (18, 0), (), "KeepCharshape", None),
        "KeepParashape": (28711, 2, (18, 0), (), "KeepParashape", None),
        "KeepSection": (28709, 2, (18, 0), (), "KeepSection", None),
        "KeepStyle": (28712, 2, (18, 0), (), "KeepStyle", None),
        "filename": (28703, 2, (8, 0), (), "filename", None),
    }
    _prop_map_put_ = {
        "FileArg": ((28705, LCID, 4, 0), ()),
        "FileFormat": ((28704, LCID, 4, 0), ()),
        "HSet": ((1, LCID, 4, 0), ()),
        "KeepCharshape": ((28710, LCID, 4, 0), ()),
        "KeepParashape": ((28711, LCID, 4, 0), ()),
        "KeepSection": ((28709, LCID, 4, 0), ()),
        "KeepStyle": ((28712, LCID, 4, 0), ()),
        "filename": ((28703, LCID, 4, 0), ()),
    }

    def __iter__(self):
        "Return a Python iterator for this object"
        try:
            ob = self._oleobj_.InvokeTypes(-4, LCID, 3, (13, 10), ())
        except pythoncom.error:
            raise TypeError("This object does not support enumeration")
        return win32com.client.util.Iterator(ob, None)

_prop_map_get_ 딕셔너리 안에 FileName 대신 filename이 있네요.

한/글에서는 FileName이던 인자명이 파이썬 코드로 변환되면서 filename으로 변경되었네요.

파이썬 코드 안에서도 FileName을 모두 filename으로 수정하겠습니다.

(끼워넣기 함수 말고 다른이름으로 저장 함수 안에도 FileName 인자가 있네요. 이것도 수정해줍니다.)

수정을 마치고 다시 실행해보겠습니다. 

 

정상적으로 실행되었습니다.

생성된 파일을 확인해보겠습니다.

마치며

이런 프로세스는 수작업으로 진행해도 그리 오래 걸리지는 않지만,

자동화의 하이라이트는 반복에 있는 것 같아요.

레거시문서 수백 개나, 하위부서에서 매주 보내는 수십개의 문서를

탬플릿 스타일로 변경하는 작업이라면

간단히 프로그램을 하나 짜놓고 실행하면

여러분의 (거의 무의미한) 업무소요 시간을 굉장히 단축시켜 줄 것입니다.

 

그런 의미에서 마지막으로 파이썬의 반복문(for)을 통해

여러 문서의 서식을 한 번에 변경하는 프로그램으로 수정해보겠습니다.

import os
from tkinter.filedialog import askopenfilenames  # 파일 여러 개 선택 가능

import win32com.client as win32


def 한글_시작(visible=True):
    hwp_app = win32.gencache.EnsureDispatch("hwpframe.hwpobject")  # 한/글 백그라운드에서 실행
    hwp_app.XHwpWindows.Item(0).Visible = visible  # 백그라운드 해제
    hwp_app.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")  # 보안모듈 실행
    hwp_app.Run("FileOpen")  # 문서 열기 다이얼로그 실행
    doc_dir = os.path.dirname(hwp_app.XHwpDocuments.Item(0).Path)
    return hwp_app, doc_dir  # 실행된 한/글 오브젝트 리턴


def 전체삭제():
    hwp.HAction.Run("SelectAll")
    hwp.HAction.Run("DeleteBack")
    hwp.HAction.Run("DeleteBack")


def 문서삽입(문서경로):
    hwp.HAction.GetDefault("InsertFile", hwp.HParameterSet.HInsertFile.HSet)
    # hwp.HParameterSet.HInsertFile.FileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경할문서.hwp"
    hwp.HParameterSet.HInsertFile.filename = 문서경로
    hwp.HParameterSet.HInsertFile.KeepSection = 0
    hwp.HParameterSet.HInsertFile.KeepCharshape = 0
    hwp.HParameterSet.HInsertFile.KeepParashape = 0
    hwp.HParameterSet.HInsertFile.KeepStyle = 0
    hwp.HAction.Execute("InsertFile", hwp.HParameterSet.HInsertFile.HSet)


def 다른이름으로저장(저장경로):
    hwp.HAction.GetDefault("FileSaveAs_S", hwp.HParameterSet.HFileOpenSave.HSet)
    # hwp.HParameterSet.HFileOpenSave.FileName = "C:\\Users\\smj02\\OneDrive\\바탕 화면\\스타일적용\\변경한문서.hwp"
    hwp.HParameterSet.HFileOpenSave.filename = 저장경로
    hwp.HParameterSet.HFileOpenSave.Format = "HWP"
    hwp.HAction.Execute("FileSaveAs_S", hwp.HParameterSet.HFileOpenSave.HSet)


def 바꿀문서선택(기본경로):
    문서경로 = askopenfilenames(title="서식을 변경할 문서를 모두 선택해주세요.",
                            initialdir=기본경로, filetypes=[("한/글 문서", "*.hwp *.hwpx")])
    return 문서경로


def 완료메시지(문서갯수):
    msgbox = hwp.XHwpMessageBox  # 메시지박스 생성
    msgbox.string = f"총 {문서갯수} 개 문서의 서식 변경을 완료하였습니다."
    msgbox.Flag = 0  # [확인] 버튼만 나타나게 설정
    msgbox.DoModal()  # 메시지박스 보이기


def 한글종료():
    hwp.Clear(option=1)  # 1: 변경내용 버림
    hwp.Quit()


if __name__ == '__main__':
    hwp, 탬플릿경로 = 한글_시작(visible=True)  # 탬플릿문서 선택
    전체삭제()  # 탬플릿문서 본문 전부 삭제
    바꿀문서리스트 = 바꿀문서선택(탬플릿경로)  # tk 파일선택 다이얼로그창 실행하여 바꿀 문서들 선택
    for 문서 in 바꿀문서리스트:
        문서삽입(문서)  # 끼워넣기 실행
        다른이름으로저장(문서.replace(".hwp", "_서식변경.hwp"))
        전체삭제()
    완료메시지(len(바꿀문서리스트))
    한글종료()

 

위 코드를 실행한 화면입니다.

 

위의 코드를 컴파일한 실행파일(exe)과 예제파일을 첨부해 둡니다.

실습에 활용하시기 바랍니다.

스타일적용.zip
12.59MB

 

 

다음 포스팅에서는

긴 포스팅 읽느라 고생하셨습니다.

갑자기 찬물 끼얹는 것 같지만, 솔직하게 위 프로그램의 단점에 대해 이야기하자면...

이렇게 코딩을 해놓아도 스타일 적용이 안 되는 경우가 한 가지 있습니다.

바로, 기존 스타일이 적용된 문서에 수작업으로

서체나 글자크기를 임의로 변경해버리는 경우입니다.

사실 이런 경우가 대부분이겠죠...

이 때는 끼워넣기를 해도 (스타일과 다른) 기존 문서의 글자모양 정보가 남아 있게 되어서

직접 한 단락 한 단락 내려가면서 탬플릿의 스타일을 하나씩 다시 적용해야 합니다. 더 번거로운 일이 되겠죠.

다음 포스팅에서는 이런 경우에 스타일을 새로고침하는 코드를 이어서 짜보겠습니다.

 

이어지는 포스팅

2022.12.04 - [아래아한글 자동화/python+hwp 중급] - [QnA] 스타일문서를 임의로 변경한 경우 복원하기

 

[QnA] 스타일문서를 임의로 변경한 경우 복원하기

안녕하세요. 한글자동화를 이용해 문서의 서식을 바꾸는 코드를 만들고 싶어 문의드립니다. 회사에서 보고서 작업을 하는데 동일한 문서에서 서식을 다르게 하여 수정 후 내용을 첨가하여 제출

martinii.fun

 

댓글