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

GetText의 첫 단락 인덱스를 수정해야 하는 이유(최종)

by 일코 2022. 2. 8.

이번 포스팅은 개별 포스팅이 아닙니다.

아래 포스팅의 보충설명입니다.

2022.02.06 - [업무자동화/파이썬-아래아한글 자동화 기초] - GetText 실행 후에 찾아가서 수정하는 방법

 

GetText 실행 후에 찾아가서 수정하는 방법

안녕하세요? 일상의 코딩, 일코입니다. 지난 포스팅에서는, 문자열을 단락별로 탐색해서 리턴해주는 GetText라는 메서드를 소개해 드렸습니다. 2022.02.04 - [업무자동화/파이썬-아래아한글 자동화

martinii.fun

 

위의 상황을 간략히 설명드리면,

코드에는 문제가 없어 보였는데

첫 단락을 수정할 때 문자열 인덱스가 잘못되었는지

엉뚱한 곳으로 가서 코드를 반복합니다.

 

이건 사실 한/글 문서의 구조 때문에 발생하는 문제로,

GetText로 한/글 자동화를 하실 때

무조건 기억하셔야 하는 부분입니다.

버그가 아닙니다!

 

이제 그 이유와 해결방법을 간단하게 알려드리겠습니다.

 

먼저,

한/글 문서에 기본적으로 숨겨져 있는 기능에 대해 

(사실 많은 사람이 사용하지는 않는...)

두 가지를 이해하셔야 합니다.

 

1. 모든 한/글 문서의 "1"번 리스트는 "메모 내용"입니다.

사실 모든 화근은 이것 때문인데요.

한/글 문서를 생성하면 기본적으로 본문 제일 앞에

보이지는 않지만

일종의 필드인 "메모내용"이 삽입되어 있습니다.

참고로 본 포스팅을 정확히 이해하시려면
한/글 문서의 좌표체계(List, Para, Pos)에 대해 알고 계셔야 합니다.
(이 부분은 추후 포스팅으로 자세히 남기겠습니다.)

간략히 아래의 개념으로 이해하셔도 무방합니다.
List : 영역번호(본문, 표1, 표2, 머리말, 꼬리말, 주석 등...)
Para : 문단번호(한 리스트 안에서 줄바꿈으로 구분되는 문단 하나하나)
Pos : 글자인덱스(몇 번째 글자 앞인지)

 

대표적인 예로, 누름틀은 최소 16자 이상의 Pos를 점유합니다.

(누름틀 안에 안내문이나 텍스트가 들어가 있는 경우 그 문자열만큼의 인덱스가 추가됩니다.)

이해를 돕기 위해 아래의 화면을 보여드리겠습니다.

 

본문 첫번째 단락, 즉 제일 앞에

위에서 언급한 메모내용이라는 숨겨진 필드가 존재하기 때문에

본문 첫 번째 단락의 경우에만

필드가 점유하는 16자를 더해서

첫 번째 글자 앞의 좌표를 0,0,16으로 재계산해야 합니다.

(위 화면에서도 그 때문에 두 번째 글자 앞의 인덱스가 17이었습니다.)

 

그래서 이전 포스팅의 오류를 해결하기 위해서는

아래와 같이 수정해야 합니다.

(위치리스트 정의 부분입니다.)

import re

import win32com.client as win32


def 한글_진하게밑줄():
    hwp.Run("Delete")
    hwp.Run("MoveSelNextWord")
    hwp.Run("MoveSelLeft")
    hwp.XHwpDocuments.Item(0).XHwpCharacterShape.Bold = True
    hwp.XHwpDocuments.Item(0).XHwpCharacterShape.UnderlineType = 1
    hwp.HAction.Run("Cancel")


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_2.hwpx")  # 문서 열기

hwp.InitScan()  # 탐색 초기화
while True:
    상태값, 문자열 = hwp.GetText()
    if 상태값 in (0, 1):  # 문서 끝까지 탐색을 마쳤으면 0 또는 1 리턴
        # 상태값 0 : 텍스트 정보 없음
        # 상태값 1 : 리스트의 끝
        break
    else:
        hwp.MovePos(201)  # 탐색한 문단 시작으로 이동
        para = hwp.GetPos()[1]  # 좌표 중 para값을 먼저 저장하고
        위치리스트 = sorted([(para, m.start()+16) if hwp.GetPos()[0:2] == (0, 0)
                        else (para, m.start()) for m in re.finditer("\|", 문자열)], reverse=True)
        for 위치 in 위치리스트:
            hwp.MovePos(1, *위치)  # 튜플 언패킹 문법, 첫 번째 파라미터 1은 "현재 리스트"
            한글_진하게밑줄()
            hwp.ReleaseScan()  # 탐색 종료
            hwp.InitScan(Range=0x0007)  # 탐색 재시작(캐럿 위치~문서의 끝)

 

그럼 이 코드를 실행해보겠습니다.

모든 "|"를 삭제하고,

해당위치 바로 뒤의 단어 하나를 선택한 후

진하게&밑줄 서식적용하기까지

완벽하게 실행되네요^^

 

마치며

GetText까지 자유자재로 다루실 수 있게 되면

생각해낼 수 있는 거의 모든 한/글 문서의 반복 수작업을

자동화하실 수 있게 됩니다.

 

다만, GetText를 사용하는 경우 가장 아쉬운 점은,

찾아바꾸기를 활용하는 코드에 비해 다소 어렵다는 점입니다.

"파이썬으로 코딩에 입문한 후 C를 배우는 느낌?"이라는 비유가 가장 적절한 것 같습니다.

 

또, 아래와 같이 꼭! 신경써야 할 부분들도 있습니다.

 

1. 숨겨진 필드의 16칸 점유 때문에 첫 단락 pos 인덱스에는 16을 더해야 함

2. 탐색 중에 본문을 수정하면, InitScan으로 탐색을 재시작해야 함(파라미터로 0x0007을 줘야 현재 위치부터 재탐색)

3. 본문 수정시 단락 뒤의 para 또는 pos 인덱스가 바뀔 수 있음(역순 수정 필수)

4. 누름틀이 있는 문서는 GetText로 찾아바꾸기 어려움(누름틀 자체가 인덱스를 16개 이상 먹기 떄문)

 

이런 이유들 떄문에

"찾아바꾸기 매크로"에 비하면

GetText로 짜는 자동화 코드는

거의 동일한 기능을 구현함에도

직관적이지도 않게 되고,

작성하는 코드의 양도 굉장히 늘어나게 됩니다.

 

그럼에도 불구하고

GetText로만 구현가능한 자동화 기능들이 있기 때문에

간략히 소개를 드렸습니다.

 

그럼 이것으로 GetText에 대한 포스팅을 마치겠습니다.

수고하셨습니다.

 

 

부록

"위치리스트"를 정의한 코드(라인30~31) 부연설명

위치리스트 = sorted([(para, m.start()+16) if hwp.GetPos()[0:2] == (0, 0)
                        else (para, m.start()) for m in re.finditer("\|", 문자열)], reverse=True)

 

길어봐야 두 줄인 코드지만,

여러 가지 함수와 메서드가 뒤죽박죽 되어 있어

이 두 줄을 이해하려면 암호해독하는 느낌이 들 수도 있습니다...

위의 코드를 풀어쓰면 아래와 같습니다.

위치리스트 = []  # 빈 리스트를 하나 만들고

for m in re.finditer("\|", 문자열):  # 문자열 중 "|"를 찾는다. "|"를 메타문자로 인식하지 않기 위해 앞에 "\"를 붙임.
    if hwp.GetPos()[0:2] == (0, 0):  # 이 때 탐색중인 단락이 본문 첫 번째 단락이면
        pos = m.start() + 16  # pos인덱스에 16을 더하고, (숨겨진 메모내용 필드가 16칸을 차지하고 있음)
    else:  # 다른 모든 단락에는 
        pos = m.start()  # 16을 더하지 않는다.
    위치 = (para, pos)  # 단락번호와 글자인덱스를 튜플로 저장한 후 
    위치리스트.append(위치)  # 위치리스트에 하나씩 더한다.

위치리스트.sort(reverse=True)  # 역순으로 정렬한다.
#  끝.

 

기관 재직 시절에는

혼자 코딩 배우고 자동화 공부하고

혼자 코드 짜서 혼자서만 활용했거든요.

그러다 보니까 가독성이나 이해 측면을 전혀 고민할 필요가 없었어요..

 

근데 포스팅을 계속 남기다 보니까

한/글 자동화를 독학하시는 분들,

또 질문 주시는 분들을 의식하게 되고

저를 많이 돌아보게 되더라고요.

제 딴에는 어렵게 배운 밑천 탈탈 털어서

무료로 자동화 알려드린답시고

유튜브 영상이나 블로그 포스팅을 끄적거려봤거든요.

근데 독자 입장에서 생각해보니까

내 포스팅을 누가 읽고 이해할 수 있겠나? 싶더라고요.

 

그래서

보다 읽기 쉽고 재미있는 포스팅 작성할 수 있게

고민하고 연구해보겠습니다.

 

읽어주셔서 감사합니다.

행복한 하루 되세요!

일코 드림

 

댓글