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

찾기로 탐색한 단어 말고 바로 뒤의 단어를 조작하고 싶다면?

by 일코 2022. 2. 3.

Quitryne님께서 질문 주신 내용

안녕하세요? 일상의 코딩, 일코입니다.

(편하게 일코님~ 하고 불러주세요^^)

 

이번에 질문 주신 내용을 요약하면

"문자열 탐색 후 옆 단어를 조작하는 방법"

입니다.

 

지난 포스팅에서 "찾아바꾸기"를 파이썬으로 활용하는 방법을 보여드렸는데,

이번 포스팅은 그 응용편이라고 보셔도 좋겠습니다.

 

먼저 드리고 싶은 말씀은,

"이걸 구현하는 방법은 다양하다"는 것입니다.

입맛대로 어떤 방법이든 사용하셔도

왠만하면 대부분 잘 돌아갈텐데,

 

그럼에도 제가 가장 추천하는 두 가지 방법은,

 

1. 찾기(Ctrl-F) 매크로 아래에 매크로 덧붙이기

2. GetText를 활용해서 찾아간 후 조작하는 방법

 

입니다.

 

 

아니, 굳이 두 가지를?;;;;;

 

첫 번째 방법(찾아바꾸기 + 액션 덧붙이기)은 제 경험상,

아주아주 가끔씩 캐럿이 다른 위치로 튀는 버그(?)가 있지만

코드가 짧고 직관적이어서, 나름 구현하기 쉽다는 장점이 있고,

 

두 번째 방법(GetText 후 캐럿 이동)은

약간 코드가 길어지는 단점이 있는 반면에

훨씬 유연하고, 강건한 프로그램이 된다는 장점이 있습니다.

(그렇다고 첫 번째 코드가 버그투성이라는 뜻은 아닙니다ㅜ)

 

이번 포스팅에서는 첫 번째 방법(찾기 활용)을 먼저 보여드리겠습니다.

 

예제파일은 Quitryne님께서 댓글로 남겨주신 문자열을 한/글로 옮긴 파일입니다.

이 튜토리얼을 따라해 보고 싶으신 분은 아래 첨부된 hwpx 파일을 다운받아 주시기 바랍니다.

food_unites.hwpx
0.03MB

 


 

질문 주신 작업을 짧게 요약하면 아래와 같습니다.

1. 문서의 모든 "|"를 찾아서 지우고
2. 해당 위치 바로 다음의 단어를 하나 선택해서
3. 진하게, 밑줄 속성 적용

 

0. 파이썬으로 한/글 열기

제 포스팅을 처음 접하시는 분들을 위해 파이썬으로 한/글을 여는 코드를 아래 적어둡니다.

import win32com.client as win32

hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
hwp.XHwpWindows.Item(0).Visible = True
hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
hwp.Open(r"C:\Users\smj02\Desktop\food_unites.hwpx")
- 위 코드 중 RegisterModule 라인은 레지스트리에 보안모듈을 등록하셔야 실행됩니다.
- 보안모듈을 등록하지 않으셔도 자동화 작업에 큰 지장은 없습니다.
- 저는 PyCharm을 사용합니다.
- 에디터창에 코드를 작성해 가면서, 원하는 코드만 콘솔에서 실행합니다.
- 파이참에서 선택한 범위나, 현재 라인의 코드만 실행하는 단축키는 <Shift-Alt-E> 입니다.

 

 

 

 

 

1. 찾기 작업

한/글의 찾기 스크립트를 녹화해보겠습니다. (Shift-Alt-H)

찾기 1번 실행 후 스크립트 녹화 종료

복사한 코드는 아래와 같습니다.

function OnScriptMacro_중국어1성() {
    HAction.GetDefault("RepeatFind", HParameterSet.HFindReplace.HSet);
    with (HParameterSet.HFindReplace) {
        ReplaceString = "";
        FindString = "|";
        IgnoreReplaceString = 0;
        IgnoreFindString = 0;
        Direction = FindDir("Forward");
        WholeWordOnly = 0;
        UseWildCards = 0;
        SeveralWords = 0;
        AllWordForms = 0;
        MatchCase = 0;
        ReplaceMode = 0;
        ReplaceStyle = "";
        FindStyle = "";
        FindRegExp = 0;
        FindJaso = 0;
        HanjaFromHangul = 0;
        IgnoreMessage = 1;
        FindType = 1;
    }
    HAction.Execute("RepeatFind", HParameterSet.HFindReplace.HSet);
}

 

이 스크립트를 파이썬에서 돌아가는 코드로 바꾸는 작업은 아래와 같습니다.

굉장히 간단한데, 글로 설명하긴 좀 장황해서, 동영상으로 찍어 올려봅니다.

핵심은 두 가지,

모든 변수 앞에 hwp를 붙이고, with문을 빼는 것입니다.

이젠 익숙해져서 1분도 채 안 걸리네요..

여기서 불필요한 속성을 조금만 더 솎아내고, 함수로 만들었습니다.

def 한글_찾기(문자열):
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.FindString = 문자열
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)

 

이 함수를 실행해보겠습니다.

한글_찾기 함수 반복실행하는 모습

의도대로 잘 탐색해주네요. 이건 패스할게요.

 

2. 찾은 문자 지우고, 뒤의 단어 진하게&밑줄

파이프("|")를 삭제하고 다음 단어를 선택(끝의 스페이스는 제외)한 후

진하게, 밑줄을 적용하는 과정을 매크로로 녹화해보겠습니다.

하여튼 상당히 간단하네요.

이걸 파이썬 함수로 만들어보겠습니다.

(변환과정은 생략합니다.)

def 한글_진하게밑줄():
    hwp.HAction.Run("DeleteBack")
    hwp.HAction.Run("MoveSelNextWord")
    hwp.HAction.Run("MoveSelLeft")
    hwp.HAction.Run("CharShapeBold")
    hwp.HAction.Run("CharShapeUnderline")
    hwp.HAction.Run("Cancel")

 

두 함수를 모두 완성했습니다.

 

3. 한 번씩 수동으로 실행해보겠습니다. 

(자세히 보시면 마지막에 문제가 발생합니다.)

 

마지막 단어가 한글자씩 지워지는 문제가 있음

 

 

종료시점을 정확하게 정의하지 않고 무작정 반복해버리면

마지막 찾은 단어를 훼손(뒷글자부터 하나씩 삭제)해버리네요?

 


 

4. 이 문제는 어떻게 해결할 수 있을까요?

코드는 위에서 다 완성한 줄 알았는데,

필요 이상으로 코드를 반복하면 오류가 발생한다는 사실을 알아냈습니다.

 

그럼 정확한 반복횟수를 어떻게 알아낼 수 있을까요?

 

이것도 두 가지 방법이 있습니다.

첫 번째는 문서 안에 "" data-link="") 5

[##_Image" data-link-islinknewwindow="false" "가 몇 개인지 미리 파악

해서 반복횟수를 정하는 방법이 있고, (코드 간편)

두 번째는 한글_찾기 함수 내부의 마지막 라인이 True/False를 반환하는 점을 활용하는 방법입니다.

 

조금 빙~돌아가는 느낌이지만 두 가지 다 테스트해보겠습니다. (안 읽고 넘어가셔도 괜찮습니다.)

 

(1) ">

[##_Image" target="Array">

 

녹화한 매크로는 아래와 같습니다.

function OnScriptMacro_중국어1성()
{
	HAction.Run("DeleteBack");
	HAction.Run("MoveSelNextWord");
	HAction.Run("MoveSelLeft");
	HAction.Run("CharShapeBold");
	HAction.Run("CharShapeUnderline");
	HAction.Run("Cancel");
}
저는 "" "가 몇 개인지 미리 파악해서 반복횟수를 정하는 방법이 있고, (코드 간편)

두 번째는 한글_찾기 함수 내부의 마지막 라인이 True/False를 반환하는 점을 활용하는 방법입니다.

 

조금 빙~돌아가는 느낌이지만 두 가지 다 테스트해보겠습니다. (안 읽고 넘어가셔도 괜찮습니다.)

 

(1) "/>"의 갯수를 미리 파악하는 방법입니다.

먼저 파이썬에서 아래 코드를 실행해봅니다.

>>> data = hwp.GetTextFile("UNICODE", "")
>>> data.count("
hwp.GetTextFile은 문서의 모든 글자를 문자열로 가져옵니다.
"UNICODE" 옵션을 주면, 화면처럼 (서식없이) 유니코드 문자만 추출해 오고요.

이제 "|"가 5번 사용되었다는 정보를 알아냈으니

아래와 같은 방식으로

파이썬의 for문 안에서 활용할 수 있겠네요.

count = hwp.GetTextFile("UNICODE", "").count("|")  # 5
for i in range(count):
    한글_찾기("|")
    한글_진하게밑줄()

사실 이러면 끝이죠..ㅎ

그래도 다른 방법까지 마저 설명드리겠습니다.

 

(2) hwp.Execute 가 리턴하는 True/False 정보를 활용하는 방법입니다.

찾기 매크로 실행시 마지막의 hwp.Execute는

계속해서 True를 리턴하다가

정확히 한 바퀴 돌고 난 시점에는

찾기를 수행하지 않고 False를 리턴합니다.

(위에 첫 번째로 정의한 함수인 한글_찾기에서는 그 정보를 무시했거든요..)

 

이 불리언(True/False) 값은

파이썬의 while문 안에서

반복종료(break) 조건에 활용할 수 있습니다.

무슨 뜻인지 아래 화면을 한 번 가만히 보시죠.

찾기 매크로가 마지막에 False를 리턴함

 

찾기 매크로가 계속 True를 리턴하다가

마지막에만 False를 리턴하는 거 보이시죠!? (한 바퀴 돌았을 때)

이 정보를 활용하면

문서 사이즈가 아무리 크더라도

메모리나 속도 걱정 없이 코드를 반복할 수 있겠네요.

 

그럼 한글_찾기 함수를 아래와 같이 조금만 수정해볼게요.

def 한글_찾기(문자열):
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.FindString = 문자열
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    cond = hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    return cond

 

이걸 활용한 코드는 아래와 같은 모양이 되겠지요?

while True:
    if 한글_찾기("|"):
        한글_진하게밑줄()
    else:
        break

 

(간단한 꼼수가 좀 섞였지만) 코드가 엄청 짧아졌네요?

 


 

5. 그럼 최종 코드를 하나씩 실행해보겠습니다.

 

(1) 파이썬의 for문과 hwp.GetTextFile을 활용한 코드입니다.

for문과 GetTextFile을 활용한 방법

의도대로 잘 작동하네요!

 

(2) 파이썬의 while문과 hwp.Execute의 리턴값을 활용한 코드입니다.

while문과 hwp.Execute의 리턴값을 활용한 방법

이 코드도 잘 작동하네요.

 

그럼 while문을 활용한 코드를 아래와 같이 손을 좀 본 후에

컴파일을 해보겠습니다. (pyinstaller -F -w 파일명)

import os
from tkinter import Tk
from tkinter.filedialog import askopenfilename
import win32com.client as win32


def 한글_열기(한글파일):
    hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
    hwp.XHwpWindows.Item(0).Visible = True
    hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
    hwp.Open(한글파일)
    return hwp


def 한글_찾기(문자열):
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.FindString = 문자열
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    cond = hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    return cond


def 한글_진하게밑줄():
    hwp.HAction.Run("DeleteBack")
    hwp.HAction.Run("MoveSelNextWord")
    hwp.HAction.Run("MoveSelLeft")
    hwp.HAction.Run("CharShapeBold")
    hwp.HAction.Run("CharShapeUnderline")
    hwp.HAction.Run("Cancel")


if __name__ == '__main__':
    os.chdir(os.path.join(os.environ["USERPROFILE"], "desktop"))
    BASE_DIR = os.getcwd()
    root = Tk()
    한글파일 = askopenfilename(title="교정할 HWP 파일을 선택해 주세요.",
                           initialdir=BASE_DIR,
                           filetypes=[("아래아한글", "*.hwp *.hwpx")])
    root.destroy()
    hwp = 한글_열기(한글파일)

    while True:
        if 한글_찾기("|"):
            한글_진하게밑줄()
        else:
            break

 

6. 위 코드를 pyinstaller로 컴파일해서 실행파일로 만들었습니다.

위 코드를 pyinstaller로 컴파일한 실행파일은 압축해서 아래에 첨부해 둡니다.

찾아서굵게밑줄.zip
12.68MB

(여전히 손보고 싶은 부분이 많지만, 시간도 없고 욕심도 버렸습니다.)

최종 실행화면은 아래와 같습니다.

 

마치며

부디 컴파일해드린 실행파일만 활용하지 마시고,

위에 설명드린 코드를 학습자료로 활용하셔서

여러분의 소중한 시간과 에너지를 갉아먹는

귀찮고 번거롭고, 짜증나는 업무들을

하나씩 직접 자동화해보시기를 바랍니다.

 

제가 최대한 도움 드리겠습니다.

블로그에 올리는 포스팅도

가급적이면 입문자를 위한 따라하기 방식으로

단순화해서 업로드할 예정이니

기존 포스팅도 많이 참고하시기 바랍니다.

 

그럼 행복한 하루 되시고,

일코하세요!

 

일코 드림

 

댓글