본문 바로가기
업무자동화/파이썬-아래아한글 자동화 기초

5. 특정단어 포함한 문장 전체의 서식 바꾸기, 가능할까?

by Martinii의 회사원코딩 2020. 8. 13.

안녕하세요?

회사원코딩의 Martinii입니다.

이번 포스팅에서는 아래아한글의 "찾기/찾아바꾸기"를 파이썬 코드로 재현해보려고 합니다. 나아가 특정 단어를 포함한 문장 전체를 선택해서 서식을 바꾸는 작업까지 코딩해볼게요.

 

한/글에서 기본적으로 찾기는 Ctrl-F, 찾아바꾸기는 Ctrl-H죠.

예문은 아래 사이트에서 5문단 정도 따왔습니다.

 

한글 Lorem Ipsum (간세네)

로렘 입숨(lorem ipsum; 줄여서 립숨, lipsum)은 출판이나 그래픽 디자인 분야에서 폰트, 타이포그래피, 레이아웃 같은 그래픽 요소나 시각적 연출을 보여줄 때 사용하는 표준 채우기 텍스트로, 최종 �

guny.kr

출처 : http://guny.kr/stuff/klorem/

이 문서에서 가장 먼저 출현하는 "대통령"을 찾는다든지, 문서 내 모든 "대통령"이라는 텍스트에 [진하게 / 글자색 빨강]을 적용하는 것은 코딩 없이도 금방 할 수 있죠. 문서 분량이 아무리 많더라도요. 아래 과정만 하면 돼요.

간단하게 "대통령" 찾기. Ctrl-F
Ctrl-H로 찾아 바꾸기 선택한 후 바꿀내용의 바꿀글자모양(C) 선택하고
속성:진하게, 글자색:빨강 두 가지를 선택한 후 설정(D)을 누르고 모두 바꾸기(A)를 선택하면
모든 "대통령"이라는 단어에 빨간색, 진하게 속성을 적용했다. 간단함.

근데 이런 작업은 어떨까요?

"대통령"이라는 글자가 들어가는 모든 문장에 [진하게/글자색 빨강] 적용하기라면?

이런 작업은 경우가 달라지죠. 문서분량이 많아질수록 작업시간이 길어지는 작업이 돼요. 이럴 때는 코딩을 사용하면 훨씬 수월할 수 있어요. 코드도 간단하고요. 방법이 여러 가지가 있겠지만, 우선 제가 한 가지 방법을 자세히 설명해드리고, 하단에 추가로 가능한 몇 가지 방법들을 간단히 알려드려볼게요. 보고 연습해 보시는 분은 댓글로 직접 작성한 코드 남겨주시면 피드백 남겨드릴게요.

우선 가장 먼저 생각나는 방법은 이것 같아요.

1. 문서 시작부분으로 이동한 후에

hwp.MovePos(2)  # 문서시작으로 이동

2. 아랫방향 탐색으로 "대통령"을 찾는다.

아랫방향 찾기로 "대통령" 탐색

3. 그 상태에서 윗방향 탐색으로 "."을 찾는다. 오른쪽으로 한 칸 이동하고 그 위치를 start_pos에 저장한다.

여기서 우측으로 한 칸 이동(다음 문장의 첫 글자 앞)

4. 그 상태에서 아랫방향 탐색으로 "."을 찾는다. 오른쪽으로 한 칸 이동하고 그 위치를 end_pos에 저장한다.

"대통령"이 들어간 문장의 종료지점.

5. hwp.SetPos(*start_pos) 로 시작부분으로 이동한다.

6. Run("Select") 로 선택모드 실행한 후, hwp.SetPos(*end_pos)로 문장 전체를 선택한다.

"대통령"이 들어간 문장 하나를 선택했다.

7. 선택한 부분의 서식을 변경한다. start_pos를 pos_set이라는 세트에 저장한다.

진하게, 글자빨간색 적용.

8. 2번부터 7번까지를 반복한다. 새 start_pos가 pos_set 안에 있으면 while문을 종료한다.

끝.

 

위의 과정을 코드로 옮겨보았습니다.

# -*- coding: utf-8 -*-

import win32com.client as win32
import os


text = "정부는 회계연도마다 예산안을 편성하여 회계연도 개시 90일전까지 국회에 제출하고, 국회는 회계연도 개시 30일전까지" \
       " 이를 의결하여야 한다. 국채를 모집하거나 예산외에 국가의 부담이 될 계약을 체결하려 할 때에는 정부는 미리 국회의" \
       " 의결을 얻어야 한다. 국회에서 의결된 법률안은 정부에 이송되어 15일 이내에 대통령이 공포한다. 계엄을 선포한 때에" \
       "는 대통령은 지체없이 국회에 통고하여야 한다. 정당은 그 목적/조직과 활동이 민주적이어야 하며, 국민의 정치적 의사" \
       "형성에 참여하는데 필요한 조직을 가져야 한다. 국가는 법률이 정하는 바에 의하여 재외국민을 보호할 의무를 진다. 국" \
       "회는 의원의 자격을 심사하며, 의원을 징계할 수 있다.\r\n선거에 있어서 최고득표자가 2인 이상인 때에는 국회의 재적" \
       "의원 과반수가 출석한 공개회의에서 다수표를 얻은 자를 당선자로 한다. 국무총리/국무위원 또는 정부위원은 국회나 그" \
       " 위원회에 출석하여 국정처리상황을 보고하거나 의견을 진술하고 질문에 응답할 수 있다. 근로자는 근로조건의 향상을 " \
       "위하여 자주적인 단결권/단체교섭권 및 단체행동권을 가진다. 국가는 재해를 예방하고 그 위험으로부터 국민을 보호하" \
       "기 위하여 노력하여야 한다. 대통령으로 선거될 수 있는 자는 국회의원의 피선거권이 있고 선거일 현재 40세에 달하여" \
       "야 한다. 국가안전보장에 관련되는 대외정책/군사정책과 국내정책의 수립에 관하여 국무회의의 심의에 앞서 대통령의 " \
       "자문에 응하기 위하여 국가안전보장회의를 둔다.\r\n국가유공자/상이군경 및 전몰군경의 유가족은 법률이 정하는 바에 " \
       "의하여 우선적으로 근로의 기회를 부여받는다. 모든 국민은 소급입법에 의하여 참정권의 제한을 받거나 재산권을 박탈" \
       "당하지 아니한다. 국가는 노인과 청소년의 복지향상을 위한 정책을 실시할 의무를 진다. 국민경제자문회의의 조직/직무" \
       "범위 기타 필요한 사항은 법률로 정한다. 모든 국민은 인간다운 생활을 할 권리를 가진다. 여자의 근로는 특별한 보호" \
       "를 받으며, 고용/임금 및 근로조건에 있어서 부당한 차별을 받지 아니한다. 연소자의 근로는 특별한 보호를 받는다. 대" \
       "통령은 법률에서 구체적으로 범위를 정하여 위임받은 사항과 법률을 집행하기 위하여 필요한 사항에 관하여 대통령령을" \
       " 발할 수 있다.\r\n대통령은 조약을 체결/비준하고, 외교사절을 신임/접수 또는 파견하며, 선전포고와 강화를 한다. 국" \
       "무위원은 국정에 관하여 대통령을 보좌하며, 국무회의의 구성원으로서 국정을 심의한다. 법관이 중대한 심신상의 장해" \
       "로 직무를 수행할 수 없을 때에는 법률이 정하는 바에 의하여 퇴직하게 할 수 있다. 대통령은 국가의 원수이며, 외국에" \
       " 대하여 국가를 대표한다. 국무총리는 대통령을 보좌하며, 행정에 관하여 대통령의 명을 받아 행정각부를 통할한다. 국" \
       "회의원과 정부는 법률안을 제출할 수 있다. 이 헌법은 1988년 2월 25일부터 시행한다. 다만, 이 헌법을 시행하기 위하" \
       "여 필요한 법률의 제정/개정과 이 헌법에 의한 대통령 및 국회의원의 선거 기타 이 헌법시행에 관한 준비는 이 헌법시" \
       "행 전에 할 수 있다.\r\n국회는 상호원조 또는 안전보장에 관한 조약, 중요한 국제조직에 관한 조약, 우호통상항해조약" \
       ", 주권의 제약에 관한 조약, 강화조약, 국가나 국민에게 중대한 재정적 부담을 지우는 조약 또는 입법사항에 관한 조약" \
       "의 체결/비준에 대한 동의권을 가진다. 대통령은 법률이 정하는 바에 의하여 사면/감형 또는 복권을 명할 수 있다. 모" \
       "든 국민은 헌법과 법률이 정한 법관에 의하여 법률에 의한 재판을 받을 권리를 가진다. 정당의 목적이나 활동이 민주적" \
       " 기본질서에 위배될 때에는 정부는 헌법재판소에 그 해산을 제소할 수 있다.\r\n"


def init_hwp():
    """
    한/글 열고 보안모듈 실행하고 백그라운드작업 해제하는 함수
    사용법: hwp = init_hwp()
    :return: hwp객체
    :rtype: object
    """
    hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")  # 한/글 열고
    hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")  # 보안모듈 실행
    hwp.XHwpWindows.Item(0).Visible = True  # 백그라운드작업 해제
    return hwp


def type_text(word):
    """
    한/글창에 문자열을 입력하는 함수
    :param word: 입력할 문자열
    :type word: str
    :return: None
    :rtype: None
    """
    hwp.HAction.GetDefault("InsertText", hwp.HParameterSet.HInsertText.HSet)  # 메서드 초기화
    hwp.HParameterSet.HInsertText.Text = word  # 파라미터에 문자열 word 추가
    hwp.HAction.Execute("InsertText", hwp.HParameterSet.HInsertText.HSet)  # 메서드 실행
    return  # 함수 종료


def find_word(word, direction="Forward"):
    """
    문서 내에서 word를 검색하는 함수
    :param word: 검색할 문자열
    :type word: str
    :param direction: 검색할 방향 ["BackWard", "Forward"]
    :type direction: str
    :return: None
    :rtype: None
    """
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir(direction)
    hwp.HParameterSet.HFindReplace.FindString = word
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    status = hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    return


def 특정단어포함문장선택(target="대통령", end="."):
    """
    특정 단어를 포함한 문장 전체를 선택하는 함수. 문장구분은 "."가 기본값임.
    :param target: 검색할 문자열
    :type target: str
    :param end: 문장 구분 단위. 기본은 온점 "."
    :type end: str
    :return: start_pos 튜플
    :rtype: tuple
    """
    find_word(target, "Forward")
    find_word(end, "Backward")
    hwp.Run("MoveRight")
    start_pos = hwp.GetPos()
    find_word(end, "Forward")
    end_pos = hwp.GetPos()
    hwp.Run("Cancel")
    hwp.SetPos(*start_pos)
    hwp.Run("Select")
    hwp.SetPos(*end_pos)
    return start_pos


def 선택구간속성변경(color="Red", bold=True):
    """
    선택한 구간의 속성을 변경하는 함수.
    :param color: 글자색
    :type color: str
    :param bold: 진하게할지말지
    :type bold: bool
    :return: None
    :rtype: None
    """
    if bold:
        hwp.Run("CharShapeBold")
    hwp.Run(f"CharShapeTextColor{color}")
    hwp.Run("Cancel")
    return


if __name__ == '__main__':
    hwp = init_hwp()
    type_text(text)
    hwp.MovePos(2)
    pos_set = set()
    while True:
        pos = 특정단어포함문장선택("대통령", ".")
        if pos not in pos_set:
            pos_set.add(pos)
        else:
            break
        선택구간속성변경("Red", bold=True)
    hwp.SaveAs(os.path.join(os.getcwd(), "example.hwp"))

 결과는 아래와 같습니다.

원하는 모양대로 구현이 되었다.

 

위 코드 중에 잘 이해가 안 되거나 궁금한 부분은 댓글로 문의 남겨주시기 바랍니다.

주석이나 텍스트 때문에 코드가 조금 길어졌지만, 코드 자체는 간단합니다.

다만 파이썬 문법이나 한/글API가 생소한 분들은 좀 부담스러울 수 있겠네요..

여러분의 IDE로 옮겨서 한 번 실행해보시거나, BreakPoint를 아무 데나 하나 걸고 디버깅을 해보시면서 과정을 살펴보시면 훨씬 빠르고 쉽게 이해가 될 것입니다.

이번 포스팅은 여기서 마치겠습니다.

 

댓글4

  • ㅇㅇㅇ 2021.01.13 19:57

    한/글에서 F3를 세번 누르면 단어가 포함된 문장이 선택되는 기능이 있던데 그 기능을 활용하여 자동화할 수는 없을까요?
    답글

  • 별포스정 2021.05.01 17:17 신고

    hwp.SetPos(*start_pos)
    hwp.Run("Select")
    hwp.SetPos(*end_pos)

    위 코드 의미는 대충 시작위치부터 마지막 위치까지 구간을 선택하라는 의미인 것 같은데. *start_pos와 *end_pos의 "*"은 무슨 의미인지 좀 알려주시면 감사하겠습니다. 한글 api 문서를 봐도 안나오는 것 같구요.
    답글

    • 별포스정님 안녕하세요?ㅎ
      *은 파이썬 문법입니다. start_pos는 튜플(또는 리스트)인데, 이런 자료형을 개별 파라미터로 풀어서 함수 인자에 넣는 방법입니다. 별을 안넣고 쓰시면 오류가 발생하고요. "파이썬 튜플 언패킹"으로 구글링하시면 상세한 설명과 예제가 많으니 한 번 참고해 주시기 바랍니다.

      행복한 하루 되세요^^

  • 별포스정 2021.05.03 20:05 신고

    설명 정말 감사합니다. 위 코드를 이해하는 데에 많은 도움이 되었습니다. 튜플로 된 값들을 SetPos에 개별 인자로 넣기 위해서 파이썬의 언패킹을 이용하셨군요~~. 한/글 자동화에 관심이 가서 이렇게 마티님 티스토리까지 와서 탐독하고 있습니다. 질문이 많아지더라고 느그러이 양해 부탁드립니다.^^
    답글