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

[QnA] 한글에서 특정 글꼴 찾아 태그 붙이기

by 일코 2022. 9. 6.

안녕하세요.
우연히 유튜브에서 회사원코딩의 파이썬 hwp 강좌를 보다가 궁금한 점이 있어서 연락드리게 됐습니다.
하나씩 따라하다 보니 아주 간단한 것은 응용이 되는 데, 아래 내용은 아무리 검색을 해봐도 비슷한 내용이 안보입니다.
한글파일에서 아래 그림처럼 함초롱바탕글 내에 고딕이라는 특정 글꼴 모양이 있는 경우, 그 특정 글꼴을 찾아 앞 뒤로 태그를 붙여주고 싶은데 어떻게 하면 되는 지 궁금합니다.
문서가 많은 상태라 꼭 자동화 하고 싶습니다.
감사합니다.

함초롱바탕_고딕.hwpx
0.03MB

우선 위 문제를 해결하기 위한 알고리즘을 생각해봅시다.

(제 해법이 가장 좋은 방법은 아닐 수 있음을 먼저 밝힙니다ㅎㅎㅎ)

 

위 문제를 해결하려면 크게 두 개의 과정이 필요합니다.

(1) 문서를 순회하면서 고딕 서체가 적용된 블록구간의 좌표를 모두 얻는다.

(2) 해당 블록구간 앞뒤로 태그를 붙인다. 끝.

이 두가지 과정을 중점으로 코드 설명을 드리겠습니다.

 

1. 고딕체 구간 찾기

무식하게 한 글자 한 글자 순회하며 폰트속성을 조회할 수도 있겠지만ㅠ

다행히 한/글의 찾아바꾸기 기능 중에는 특정 서체를 탐색조건으로 설정 가능합니다.

이를 매크로녹화하면 아래처럼 코드가 만들어지는데요.

HAction.GetDefault("RepeatFind", HParameterSet.HFindReplace.HSet);
	with (HParameterSet.HFindReplace)
	{
		MatchCase = 0;
		AllWordForms = 0;
		SeveralWords = 0;
		UseWildCards = 0;
		WholeWordOnly = 0;
		AutoSpell = 1;
		Direction = FindDir("Backward");
		IgnoreFindString = 0;
		IgnoreReplaceString = 0;
		FindString = "";
		ReplaceString = "";
		FindCharShape.FontTypeUser = FontType("HFT");
		FindCharShape.FaceNameUser = "명조";
		FindCharShape.FontTypeSymbol = FontType("HFT");
		FindCharShape.FaceNameSymbol = "명조";
		FindCharShape.FontTypeOther = FontType("HFT");
		FindCharShape.FaceNameOther = "명조";
		FindCharShape.FontTypeJapanese = FontType("HFT");
		FindCharShape.FaceNameJapanese = "고딕";
		FindCharShape.FontTypeHanja = FontType("HFT");
		FindCharShape.FaceNameHanja = "명조";
		FindCharShape.FontTypeLatin = FontType("HFT");
		FindCharShape.FaceNameLatin = "고딕";
		FindCharShape.FontTypeHangul = FontType("HFT");
		FindCharShape.FaceNameHangul = "고딕";
		ReplaceMode = 1;
		IgnoreMessage = 1;
		HanjaFromHangul = 0;
		FindJaso = 0;
		FindRegExp = 0;
		FindStyle = "";
		ReplaceStyle = "";
		FindType = 1;
	}
	HAction.Execute("RepeatFind", HParameterSet.HFindReplace.HSet);

매크로녹화한 코드 중 불필요한 부분은 삭제하고 파이썬 문법으로 고치면 이렇게 바뀝니다.

def 서체찾기(서체명):
    """
    역순 찾아바꾸기 매크로 녹화한 코드
    마지막 hwp.Execute라인은 매번 True를 리턴하다 한 바퀴를 다 돌면 False를 리턴한다.
    이를 리턴값 state로 함수밖으로 빼서 while문 조건에 활용할 수 있음.
    """
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Backward")
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeJapanese = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameJapanese = 서체명
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeLatin = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameLatin = 서체명
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeHangul = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameHangul = 서체명
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    state = hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    return state
ㅇ 저는 정방향보다 역방향 탐색을 많이 사용하는데, 그 이유는 좌표를 찾아가며 작업할 경우 정방향 탐색을 해버리면 수정 지점 이후의 문서좌표가 바뀔 수 있기 때문입니다. 역순으로 탐색수정하면 수정지점 이전 문서의 좌표는 바뀌지 않지요.

ㅇ 위의 함수정의에서 굳이 state 변수를 지정해서 리턴한 이유는, 한/글 찾아바꾸기 메서드는 검색을 반복하다 한 바퀴를 다 도는 시점에 False를 리턴하고, 그 외에는 전부 True를 리턴하기 때문에, 파이썬의 while문 조건에 활용하기 좋습니다.

 

서체찾기 함수는 반복실행해야 하기 때문에 for문이나 while문 안에 넣어야 하는데, state 변수(True/False)를 활용해서 while문으로 코드를 짜겠습니다. 

 

2. 찾아낸 구간의 좌표 구하기

찾아바꾸기를 한 번 실행하면, 발견한 구간을 블록선택한 상태입니다.

블록선택구간의 좌표를 리턴하는 API 명령어가 있는데, 바로 GetSelectedPos() 입니다.

위 예시에서는 (True, 0, 1, 60, 0, 1, 62) 를 리턴했는데요.

(블록위치정보를 얻어오는 데 성공했다는 True를 제외하고)

시작위치는 (리스트0, 문단번호1, 글자순서60), 끝위치는 (0, 1, 62) 임을 알 수 있습니다.

위 여섯 개의 원소를 가진 튜플을 반환하는 함수를 만들어 둡시다.

def 블록좌표():
    """
    블록의 시작 좌표와 끝좌표를 리턴한다.
        한/글문서의 좌표는 3차원 벡터 형식이다.
        (List, Paragraph, Position) 으로,
        각각 리스트번호, 문단번호, 몇번째 글자인지 나타낸다.
    """
    _, slist, spara, spos, elist, epara, epos = hwp.GetSelectedPos()
    return slist, spara, spos, elist, epara, epos

참고로, 위 좌표로 캐럿을 옮기려면 아래처럼 hwp.SetPos 메서드를 사용하면 됩니다. (곧 사용합니다.)

 

(중간점검 차원에서)

우리가 만들 최종함수의 형태는 아래처럼 될 것입니다.

if __name__ == '__main__':
    좌표리스트 = []

    # <1. 좌표수집 과정>
    while True:
        if 서체찾기(서체):  # if문에 조건으로 넣어도 함수 실행됨
            좌표리스트.append(블록좌표())
        else:
            break

    # <2. 태그삽입 과정>
    for 좌표 in 좌표리스트:
        slist, spara, spos, elist, epara, epos = 좌표
        hwp.SetPos(elist, epara, epos)
        문자열삽입(태그 + "*")
        hwp.SetPos(slist, spara, spos)
        문자열삽입(태그)

 

3. 문자열 삽입 함수

서체찾기블록좌표 함수는 완성했고,

문자열삽입은 이제 매크로 녹화를 통해 간단히 만듭시다.

매크로 녹화 후 아무 텍스트나 입력하고 녹화를 마치면 아래처럼 매크로 코드가 출력됩니다.

function OnScriptMacro_script5()
{
	HAction.GetDefault("InsertText", HParameterSet.HInsertText.HSet);
	HParameterSet.HInsertText.Text = "ㅁㄴㅇㄹ";
	HAction.Execute("InsertText", HParameterSet.HInsertText.HSet);
}

 

이 코드를 동일한 과정으로 파이썬 함수 형태로 고치면 아래처럼 만들어집니다.

def 문자열삽입(문자열):
    hwp.HAction.GetDefault("InsertText", hwp.HParameterSet.HInsertText.HSet)
    hwp.HParameterSet.HInsertText.Text = 문자열
    hwp.HAction.Execute("InsertText", hwp.HParameterSet.HInsertText.HSet)

 

4. 이미 태그달린 구간 배제하는 기능 추가

마지막으로 기존에 태그가 달린 구간에는 태그를 달지 않도록 처리합시다.

(이 프로그램을 여러 번 실행했을 때 이런 모양이 되어버리면 난처하겠죠ㅜ)

여러 가지 방법이 있겠지만, 일종의 꼼수로 다소 단순하게 처리하겠습니다.

탐색 시점에 블록상태일 때 블록구간이 "\b*"로 끝나는 경우에는 해당 좌표를 좌표리스트에 넣지 않는 겁니다.

우선 블록문자열은 GetText로 구하는 게 간편합니다. 아래처럼 함수를 짰습니다.

def 블록문자열():
    """
    블록 선택 상태에서 실행해야 하는 함수.
    블록구간의 문자열을 리턴한다.
    :return:
    """
    hwp.InitScan(Range=0x00ff)
    _, text = hwp.GetText()
    hwp.ReleaseScan()
    return text.strip()

 

그리고 메인함수에도 블록문자열 함수를 사용해서, 필터링할 수 있게 조금 손을 봐줍니다.

if __name__ == '__main__':
    좌표리스트 = []

    # 좌표수집 과정
    while True:
        if 서체찾기(서체):
            if not 블록문자열().endswith(태그 + "*"):  # 이미 태그처리된 구간은 건너뛰기 위함
                좌표리스트.append(블록좌표())
        else:
            break

    # 태그삽입 과정
    for 좌표 in 좌표리스트:
        slist, spara, spos, elist, epara, epos = 좌표
        hwp.SetPos(elist, epara, epos)
        문자열삽입(태그 + "*")
        hwp.SetPos(slist, spara, spos)
        문자열삽입(태그)

자, 이제 끝입니다.

임포트문과 한/글을 실행하는 함수 등을 추가해서 완성한 코드는 아래와 같습니다.

5. 최종 코드

import win32com.client as win32

서체 = "고딕"
태그 = r"\b"


def 한글실행(filepath, Visible=True):
    hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")
    hwp.XHwpWindows.Item(0).Visible = Visible
    hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
    hwp.Open(filepath)
    return hwp


def 문자열삽입(문자열):
    hwp.HAction.GetDefault("InsertText", hwp.HParameterSet.HInsertText.HSet)
    hwp.HParameterSet.HInsertText.Text = 문자열
    hwp.HAction.Execute("InsertText", hwp.HParameterSet.HInsertText.HSet)


def 서체찾기(서체명):
    """
    역순 찾아바꾸기 매크로 녹화한 코드
    마지막 hwp.Execute라인은 매번 True를 리턴하다 한 바퀴를 다 돌면 False를 리턴한다.
    이를 리턴값 state로 함수밖으로 빼서 while문 조건에 활용할 수 있음.
    """
    hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Backward")
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeJapanese = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameJapanese = 서체명
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeLatin = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameLatin = 서체명
    hwp.HParameterSet.HFindReplace.FindCharShape.FontTypeHangul = hwp.FontType("HFT")
    hwp.HParameterSet.HFindReplace.FindCharShape.FaceNameHangul = 서체명
    hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
    hwp.HParameterSet.HFindReplace.FindType = 1
    state = hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
    return state


def 블록좌표():
    """
    블록의 시작 좌표와 끝좌표를 리턴한다.
        한/글문서의 좌표는 3차원 벡터 형식이다.
        (List, Paragraph, Position) 으로,
        각각 리스트번호, 문단번호, 몇번째 글자인지 나타낸다.
    :return:
    """
    _, slist, spara, spos, elist, epara, epos = hwp.GetSelectedPos()
    return slist, spara, spos, elist, epara, epos


def 블록문자열():
    """
    블록 선택 상태에서 실행해야 하는 함수.
    블록구간의 문자열을 리턴한다.
    :return:
    """
    hwp.InitScan(Range=0x00ff)
    _, text = hwp.GetText()
    hwp.ReleaseScan()
    return text.strip()


if __name__ == '__main__':
    좌표리스트 = []

    hwp = 한글실행(filepath=r"C:\Users\smj02\Desktop\함초롱바탕_고딕.hwpx")

    # 좌표수집 과정
    while True:
        if 서체찾기(서체):
            if not 블록문자열().endswith(태그 + "*"):  # 이미 태그처리된 구간은 건너뛰기 위함
                좌표리스트.append(블록좌표())
        else:
            break

    # 태그삽입 과정
    for 좌표 in 좌표리스트:
        slist, spara, spos, elist, epara, epos = 좌표
        hwp.SetPos(elist, epara, epos)
        문자열삽입(태그 + "*")
        hwp.SetPos(slist, spara, spos)
        문자열삽입(태그)

 

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

sleep 함수를 넣어 천천히 실행해본 영상

다소 간단한 문제인데, 설명이 길어지니까 거창해진 느낌이 드네요ㅜ

함수별로 구분해서 구현해보았으니 부분부분 읽어보시고

이해가 안 되거나 부연설명이 필요한 부분은 댓글 남겨주시기 바랍니다.

도움이 되었기를 바랍니다.

 

댓글