...저는 한 열흘 전 우연히 파이썬 이라는 것을 처음 알고.
유투브에서 어느 분이 가르쳐 주신대로 파이썬을 깔아놓았고. print("hello")만 딱 한 번 해 보았을 뿐입니다.
참고로, 저는 젊어서 Dos시절 포트란, 파스칼, C 등등 당신의 프로그램 언어로 코딩을 개인적으로 많이 하기도 하였다가, 이제는 나이가 ㅇㅇ살인데 뭘 더 하겠습니까만. 그래도 하는 일이 비쥬얼베이직으로 엑셀내에서 조금씩 기억을 더듬어 가며 공부 아닌 공부를 하고 있는 사람입니다.
부탁 드렸던 방법은 제가 회사의 ㅇㅇ을 많이 손 보아야 하는 직업이라.
수년전 한글내에서 매크로로 겨우 조항 제목 문자 굵게 만들고, 제1조(상호명) () 괄호를 []대괄호로 제1조[상호명] 바꾸는 것을 시도 해 본 이후, 아마 7년전 일 같습니다. 당시 번호 바꾸는 것은 일일이 손으로 하였습니다. ..ㅠㅠ
지금은 다 잊었지만.....글쎄 되려나 모르겠지만...
최근에 제가 이 일을 다시 시작하게 되면서 선생님께서 작성해 주신 방법이 파이썬에서 구현이 되는가 며칠 동안 잠을 자며 꿈꿔 보던 것이 었습니다.
이렇게 만들어 주셔서 너무 고맙습니다.
제가 한 번 도전 해 보겠지만.
행여 선생님께서 시간이 되시는 날. 제가 시도 해 보려던 저 위에 방식을 구현 해 주신다면 더 없이 감사 함 드리겠지만, 부담 갖지 마시었으면 합니다.
이 정도 만으로도 너무 너무 감사 드리고 있습니다.
(중략)
뜻하지 않게 감사 인사 드리다가 그만 부탁 아닌 글도 써 지고 말았습니다.
부담 갖지 않으시기 바라오며
하시는 일 언제나 항상 축복 받으시고 복된 가정 되시기 기도 드리겠습니다.
다시 한 번 대단히. 진심으로 감사. 감사. 감사 드립니다.
연세가 정말 상당하심에도, 배움의 의지를 놓지 않고 계신 부분에 저도 큰 자극이 되었습니다. 메일 본문에는 훨씬 길게 양해 부탁의 말씀과 감사의 메시지를 전해 주셨고, 그 부분은 생략하였습니다.
하여튼 골자는 아래 두 개입니다.
1. 조항 제목 문자를 굵게 만드는 작업(바로 아래 포스팅에서 자세히 다뤘어요.)
2. "제1조(상호명)"을 "제1조[상호명]"으로 일괄 변경하는 작업.
이번에는 2번 작업을 자동화하는 스크립트를 짜보겠습니다.
샘플파일을 아래 업로드하였으니, 연습하실 경우에 다운받아서 활용하시기 바랍니다.
(hwp파일 출처 : 법제처 law.go.kr)
위 문서는 조항번호가 정렬되어 있으며, 조항명에 "진하게"가 적용되어 있습니다.
이제 제목 부분의 ()괄호만 []괄호로 바꿔봅시다.
우선, 코딩을 사용하지 않고 직접 바꾸는 방법에 대해 먼저 알아봅시다.
1. 기본적인 "찾아바꾸기" : 조항 제목 뿐 아니라 본문의 괄호도 모두 대괄호로 바뀌기 때문에 사용불가.
2. "조("를 "조["로 바꾸고, ") "를 "] " 로 바꾸기 : 찾아보니 본문에도 해당되는 부분이 참 많아서 사용불가.
3. 하나하나 찾아가서 직접 바꾸거나 찾아바꾸기 실행 : 오래 걸리고 무식한 방법이지만, 가능합니다.
저는 자동화를 구상할 때 이런 방식으로 개념을 먼저 잡아봅니다. 그러면 코딩하기도 참 쉬워집니다.
왜냐고요?
무식하게 하나하나 손으로 작업하는 "그 작업"만 컴퓨터한테 맡기면 되거든요.
쪼개고 쪼개서 단순화한 후에 코드로 옮기려면, 무식해 보이는 방법이 코딩하기 가장 쉽고 편합니다.
그래서 이번에도 3번 방식으로 코드를 짜 볼 예정입니다.
작업과정을 좀 더 자세히 적으면 아래와 같습니다.
1. 모든 문서에서 "제{숫자}조({글자}) "로 시작하는 부분으로 찾아가기를 실행합니다.
2. "("를 "["로 찾아바꾸기를 한 번만 실행합니다.
3. ")"를 "]"로 찾아바꾸기를 또 한 번만 실행합니다.
4. 1번 과정을 반복합니다. 언제까지? "제{숫자}조({글자}) "가 나타나지 않을 때까지.
1번 과정 : "제{숫자}조({글자})" 로 구성된 문자열 찾기
1번 과정을 진행하기 앞서 긴 고민 끝에 여러분께 "조건식(정규표현식)"에 대해 소개해 드리기로 마음먹었습니다.
조건식은 조금만 배워도 아주 유용하게 써먹을 수 있는 파워풀한 검색문법입니다.
아래아한글도 오래전부터 조건식 검색 기능을 제공하고 있었고, voidtools의 everything이나, 노트패드++ 같은 응용프로그램 다수에서도 다소 문법의 차이는 있지만, 검색에 조건식을 적용할 수 있습니다.
(근데 왜 긴 고민을 거쳤냐면, 조건식의 첫인상이 굉장히 부담스럽거든요. 복잡해 보이기도 하고...)
아래아한글에서도 버전별로 아~주약간 다른 부분이 있지만, 하여튼 "찾기"에 조건식을 활용할 수 있습니다.
여기서, 조건식을 쉽게 표현하면 "이러이런 문자열을 찾아줘" 라고 컴퓨터에게 요청하는 일종의 검색문법입니다.
우선 아래 영상을 한 번 보여드리겠습니다. 조건식으로 특정 문자열을 검색하는 과정입니다.
조건식을 아주 조금만 익혀 두셔도, 이런 고급 검색작업을 손쉽게 하실 수 있습니다.
재밌고 신기하지 않나요? (저도 업무에 조건식을 정말정말 많이 활용해 왔습니다.)
위 검색영상에 사용된 조건식 문자열은 아래와 같습니다.
^제\d+조\(.+?\)
이 조건식으로 검색하면 문단 시작부분의 "제1조(목적)" 이나 "제40조(과태료의 부과기준)" 등의 문자열만 선택해줍니다.
조금씩 잘라서 설명드리겠습니다.
^제\d+조\(.+?\)
^는 "이렇게 시작하는 문자열을 찾아줘" 라는 의미입니다.
(반대로 이렇게 끝나는 걸 찾아줘 할 때는 "....$" 를 사용하시면 됩니다.)
문단 중간에 나오는 "제\d+조\(.+?\)" 해당 문자열은 전부 무시하죠.
^제\d+조\(.+?\)
^제가 나왔으니, "제"로 시작하는 문자열을 찾아줘 라는 의미가 되겠습니다.
^제\d+조\(.+?\)
\d는 한 자리 숫자를 뜻합니다.
^제\d+조\(.+?\)
근데 뒤에 +가 붙었네요? \d+는 한 자리 이상의 숫자를 뜻합니다. 1이든 99든 1000이든 말이죠.
참고로 \는 특수한 문자를 뜻하는 일종의 약속입니다. 유식하게 "이스케이프 문자열"이라고도 부릅니다.
(실수로 ^제d+ 라는 조건식을 썼으면 한/글 검색엔진이 "제d"나 "제dddddd"같은 문자열을 찾으려고 시도했을 겁니다.)
^제\d+조\(.+?\)
\(나 \)는 순수한 "("와 ")"를 가리킵니다. \가 붙지 않은 "(", ")"는 조건식 문법에서 "그룹화"에 사용되는 글자거든요.
^제\d+조\(.+?\)
온점"."은 그냥 아무 글자 하나를 가리켜요. 조건식에서 가장 기본이 되는 문법 중 하나입니다.
^제\d+조\(.+?\)
온점.에 플러스+가 붙어있고, 괄호로 닫혀 있으니, "어떤 글자들 이후에 괄호가 닫히는 부분까지의 문자열을 찾아달라"는 요청이 되겠습니다.
참, 하나 빠진 게 있죠? 바로
^제\d+조\(.+?\)
플러스+ 뒤에 나오는 ?입니다. 또 유식한 말로 "탐욕알고리즘 해제"라고 부르기도 하는데,
닫는괄호도 조건식 문법 . 에 해당할 수 있기 때문에 원하지 않게 아래와 같은 문자열을 검색할 수도 있게 됩니다.
제39조(고유식별정보의 처리) 국토교통부장관(법 제11조제7항 및 제50조에 따라 국토교통부장관의 권한 및 업무를 위임ㆍ위탁받은 자를 포함한다)
위 문자열도 어쨌든 괄호로 끝났으니까요.
(조건식은 기본적으로 최대한 많은 문자열을 포함하는(탐욕적인greedy) 방식으로 검색하도록 설계되어 있습니다.)
조건식의 탐욕을 해제해 주는 게 바로 "?"입니다.
(그 외 다른 곳에서 쓰이는 "?"는 . 과 비슷하지만 약간 다르게 "어떤 글자가 있거나 없거나"라는 문법입니다.)
자, 그럼 본 예제에 사용된 모든 조건식의 설명을 마쳤습니다.
조건식을 사용해서 내가 원하는 문자열을 검색할 수 있는지 테스트해 보시면서 연습하시다 보면 정말 강력한 도구로 활용하실 수 있는 날이 분명 옵니다. 저는 요즘 데이터 전처리나 정제작업, CSV나 엑셀 파일 서식 정리, 파일명 검색이든 뭐든 안 쓰는 날이 거의 없습니다. 그럼 여기서 1번 과정의 설명을 마치겠습니다.
위 영상의 검색과정을 스크립트매크로로 녹화하면 아래와 같습니다.
// 스크립트 매크로 녹화한 코드(자바스크립트)
function OnScriptMacro_script5()
{
HAction.GetDefault("RepeatFind", HParameterSet.HFindReplace.HSet);
with (HParameterSet.HFindReplace)
{
ReplaceString = "제40조(";
FindString = "^제\\d+조\\(.+?\\)";
IgnoreReplaceString = 0;
IgnoreFindString = 0;
Direction = FindDir("Forward");
WholeWordOnly = 0;
UseWildCards = 0;
SeveralWords = 0;
AllWordForms = 0;
MatchCase = 0;
ReplaceMode = 0;
ReplaceStyle = "";
FindStyle = "";
FindRegExp = 1;
FindJaso = 0;
HanjaFromHangul = 0;
IgnoreMessage = 1;
FindType = 1;
}
HAction.Execute("RepeatFind", HParameterSet.HFindReplace.HSet);
}
그리고 위 코드를 파이썬으로 옮겨서 함수로 만들면 아래와 같습니다. 큰 의미 없는 라인은 삭제하고, 한/글의 인스턴스명인 hwp를 모든 변수 앞에 붙여주는 간단한 작업을 거쳤습니다. 위와 아래의 코드를 비교해보시면 이해하시기 쉬울 겁니다.
# 파이썬으로 옮긴 코드
def find_word(target_word):
hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.FindString = target_word # "^제\\d+조\\(.+?\\)"
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
hwp.HParameterSet.HFindReplace.FindRegExp = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
그럼 다음 과정으로 넘어가보겠습니다.
사실 "찾아바꾸기"는 이전 여러 포스팅에서 자주 다룬 주제이지만, 그만큼 많이 활용되기도 하는 중요한 주제라고 생각됩니다. 간단히 다시 설명드리겠습니다.
1. Shift-Alt-H를 눌러서 스크립트매크로 슬롯을 선택하고 스크립트 녹화를 시작합니다.
2. Ctrl-H를 눌러서 찾아바꾸기 창을 열고 "("를 "["로 바꾸기를 한 번 실행한 후, 매크로녹화를 종료합니다.(Shift-Alt-X)
* 이 때 찾는 문자열이 하나도 없다는 메시지가 자꾸 뜨면, (를 \(로 변경해서 검색하시거나, Ctrl-F를 눌러 조건식을 체크해제하고 한 번 검색을 실행하신 후 찾아바꾸기를 다시 실행하시면 문제가 해결됩니다.)
3. Shift-Alt-L을 열어서 해당 스크립트를 여러분의 에디터로 복사합니다. 제 경우는 아래와 같네요.
// 스크립트매크로 녹화 코드(자바스크립트)
function OnScriptMacro_script5()
{
HAction.GetDefault("ExecReplace", HParameterSet.HFindReplace.HSet);
with (HParameterSet.HFindReplace)
{
MatchCase = 0;
AllWordForms = 0;
SeveralWords = 0;
UseWildCards = 0;
WholeWordOnly = 0;
AutoSpell = 1;
Direction = FindDir("Forward");
IgnoreFindString = 0;
IgnoreReplaceString = 0;
FindString = "(";
ReplaceString = "[";
ReplaceMode = 1;
IgnoreMessage = 1;
HanjaFromHangul = 0;
FindJaso = 0;
FindRegExp = 0;
FindStyle = "";
ReplaceStyle = "";
FindType = 1;
}
HAction.Execute("ExecReplace", HParameterSet.HFindReplace.HSet);
HAction.GetDefault("ExecReplace", HParameterSet.HFindReplace.HSet);
with (HParameterSet.HFindReplace)
{
MatchCase = 0;
AllWordForms = 0;
SeveralWords = 0;
UseWildCards = 0;
WholeWordOnly = 0;
AutoSpell = 1;
Direction = FindDir("Forward");
IgnoreFindString = 0;
IgnoreReplaceString = 0;
FindString = "(";
ReplaceString = "[";
ReplaceMode = 1;
IgnoreMessage = 1;
HanjaFromHangul = 0;
FindJaso = 0;
FindRegExp = 0;
FindStyle = "";
ReplaceStyle = "";
FindType = 1;
}
HAction.Execute("ExecReplace", HParameterSet.HFindReplace.HSet);
}
실행해보시면 아시겠지만 클릭을 한 번 하면 (로 찾아가고, 한 번 더 눌러야 "["로 바뀌었죠. 그래서 자동화할 때에도 두 번 실행합니다. 위의 자바스크립트 코드를 파이썬 문법으로 고치고, 함수로 사용할 수 있게 조금 수정하면 아래와 같습니다. 속성값이 0이거나 ""인 것들은 대부분 뺐습니다.
# 파이썬 코드
def hwp_find_replace(find_string, replace_string):
hwp.Run("MoveSelNextWord")
hwp.HAction.GetDefault("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
hwp.HParameterSet.HFindReplace.FindString = find_string
hwp.HParameterSet.HFindReplace.ReplaceString = replace_string
hwp.HParameterSet.HFindReplace.ReplaceMode = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HAction.GetDefault("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
hwp.HParameterSet.HFindReplace.FindString = find_string
hwp.HParameterSet.HFindReplace.ReplaceString = replace_string
hwp.HParameterSet.HFindReplace.ReplaceMode = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.Run("Cancel")
이제 파이썬으로 한/글을 열어놓고, hwp_find_replace("(", "[") 라고 실행하면 찾아바꾸기가 한 번 실행되는 거죠.
그럼 괄호를 찾아바꾸는 2번, 3번 과정도 설명을 마친 것 같네요.
4번 과정 : 반복하는 코드
몇 번 반복해야 할지 알아내는 코드는 복잡하다면 복잡하고, 단순하다면 또 단순합니다.
1. 그냥 넉넉하게 for문 돌린다. 어차피 검색해서 안나오면 바뀌는 것도 없으니까.
for i in range(10000):
2. hwp.GetTextFile("TEXT", "")와 str.count(세고 싶은 문자열) 메서드를 사용해서 for문을 돌린다.
for i in range(hwp.GetTextFile("TEXT", "").count("\r\n제")):
3. hwp.GetText와 조건식을 활용해서 몇개조항인지 알아낸 다음 for문을 돌린다..... 흠;
더 많이 있을 것 같은데 막상 생각이 잘 안 나네요. 저는 무난하게 2번을 선택해서 진행해보겠습니다.
참고로 hwp.GetTextFile("TEXT", "")는 현재 열린 문서의 전체 텍스트를 한 번에 리턴해주는 유용한 메서드입니다.
그리고 "\r\n"은 줄바꿈(엔터)를 가리킵니다.
하여튼 반복하는 코드는 아래처럼 만들었습니다.
for i in range(hwp.GetTextFile("TEXT", "").count("\r\n제")):
코드 종합하기
위의 파이썬 코드를 모아다가 붙여넣고 돌려보았습니다.
몇 가지 오류가 있어 다소간 수정을 했는데, 궁금하신 분은 위의 코드와 최종코드를 비교해보시기 바랍니다.
소스코드 보여드리기 전에 코드 돌리는 영상 먼저 보여드립니다. 오류없이 실행되니 기분이 좋습니다.
소스코드 전체는 아래에 공개해 두었습니다. 필요하다고 생각되시면 저장해 두셨다가 활용하시기 바랍니다.
from tkinter import Tk
from tkinter.filedialog import askopenfilename
import win32com.client as win32
def hwp_init(filename):
hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
hwp.Open(filename)
hwp.XHwpWindows.Item(0).Visible = True
hwp.HAction.Run("FrameFullScreen")
return hwp
def find_word(target_word, direction="Forward"):
hwp.HAction.GetDefault("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.FindString = target_word # "^제\\d+조\\(.+?\\)"
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir(direction) # "Forward" or "Backward"
hwp.HParameterSet.HFindReplace.FindRegExp = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("RepeatFind", hwp.HParameterSet.HFindReplace.HSet)
hwp.HAction.Run("MoveLineBegin")
def hwp_find_replace(find_string, replace_string):
hwp.HAction.Run("MoveLineBegin")
hwp.HAction.GetDefault("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
hwp.HParameterSet.HFindReplace.FindString = find_string
hwp.HParameterSet.HFindReplace.ReplaceString = replace_string
hwp.HParameterSet.HFindReplace.ReplaceMode = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HAction.GetDefault("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.HParameterSet.HFindReplace.Direction = hwp.FindDir("Forward")
hwp.HParameterSet.HFindReplace.FindString = find_string
hwp.HParameterSet.HFindReplace.ReplaceString = replace_string
hwp.HParameterSet.HFindReplace.ReplaceMode = 1
hwp.HParameterSet.HFindReplace.IgnoreMessage = 1
hwp.HParameterSet.HFindReplace.FindType = 1
hwp.HAction.Execute("ExecReplace", hwp.HParameterSet.HFindReplace.HSet)
hwp.Run("Cancel")
hwp.HAction.Run("MoveLineBegin")
if __name__ == '__main__':
root = Tk()
filename = askopenfilename()
root.destroy()
hwp = hwp_init(filename=filename)
for i in range(hwp.GetTextFile("TEXT", "").count("\r\n제")):
find_word("^제\\d+조\\(.+?\\)", "Forward")
current_position = hwp.GetPos() # 현위치 저장(간혹 다음 검색위치로 튀는문제 조치)
hwp_find_replace("(", "[")
hwp.SetPos(*current_position) # 방금위치 복원
hwp_find_replace(")", "]")
* 아래에서 두 번째 줄 "*"은 리스트나 튜플 등의 배열자료를 언패킹하는 파이썬 문법입니다. 변수 current_position은 커서의 위치를 나타내는 세 개의 정수로 이뤄진 튜플이고, hwp.SetPos 메서드는 (튜플이 아닌) 세 개의 정수 파라미터를 인자로 받거든요.
재미있게 보셨거나 도움이 되셨으면 좋겠습니다. 나름대로 여러가지 노하우가 녹아 있으니, 아래아한글 자동화에 관심이 많으신 분이라면 한 번쯤은 따라해 보셔도 좋을 튜토리얼이라고 생각됩니다.
이번 포스팅은 여기서 마칩니다.
최근들어, 문의주시는 내용을 바탕으로 포스팅을 제작하고 있습니다. 제 기준으로 콘텐츠를 만드니까 의욕도 잘 생기지 않고, 가끔은 너무 매니악(?)해지기도 하는 경향이 있더라고요. 여러분 궁금하시거나 필요하신 자동화 테마 관련해서 자유롭게 의견이나 문의 주시면, 이렇게 포스팅으로 회신 드리겠습니다.
감사합니다.
행복한 하루 되세요^^
'아래아한글 자동화 > python+hwp 중급' 카테고리의 다른 글
[0/4] 정관문서 서식잡기 준비(조번호 재정렬) (0) | 2020.12.11 |
---|---|
현재 위치 글자모양에 "진하게"가 적용되어 있는지 확인하는 방법 (3) | 2020.12.07 |
[자동화문의] 표 안에 이미지 넣는 작업 (6) | 2020.12.06 |
댓글