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

[0/4] 정관문서 서식잡기 준비(조번호 재정렬)

by 일코 2020. 12. 11.
안녕하세요? 강의에서  예문으로 "통칙"이 있는데 제가 하고 싶은 것은 제1조, 제2조 제3조등이 쭉....수십개 있습니다. 그 중 예로 제3조와 제 4조 사이에 한 두개의 조항이 추가되면 이후 끝까지 조번호가 바뀌는 작업을 일일이 손으로 했습니다. 한글에서 스크립트 작성을 해 봐도 이것이 자동으로 할 수 없기에. 파이썬을 사용하면, 가능할지. 선생님의 강의를 보고는 통칙 예제가 있어 감히 조언 받고자 문의 드립니다. 이 글 보실런가 모르겠지만...꼭 보시고...도움 주시면 정말 고맙겠습니다. 제 메일은 xxx123@xavier.com 입니다. 감사합니다.

한 구독자 분으로부터 문의메일을 받고 자문을 드렸던 내용을 포스팅으로 올리고 있습니다.

일명 "정관 바로잡기" 시리즈입니다. 총 5개의 포스팅으로 연재 예정입니다.

0. [준비] 조항번호 재정렬
1. 조항번호 앞 공백 추가(제1조 -> 제__1조, 제10조 -> 제_10조)
2. 장제목 및 조항제목이 굵지 않다면 굵게
3. 장제목은 센터정렬, 장제목 위아래 빈줄, 조제목 위에 빈줄
4. 종합

 

정관의 초기 서식은 아래와 같습니다.

정관의 초기 서식

우선 조항번호가 제대로 매겨져 있지 않고, 장제목과 조제목에 진하게, 장제목은 가운데로 정렬하고 싶습니다. 기왕이면 장제목 조제목 사이에는 모두 빈 줄이 하나씩 있으면 좋겠습니다. 그렇게,

이번 시리즈를 마칠 때, 최종적으로 제가 원하는 서식은 아래와 같습니다.

정관의 최종 서식

이번 포스팅에서는 준비과정인 번호재정렬 자동화를 설명드리겠습니다. 조항을 하나 추가하거나 삭제했을 때, 그 아래로 모든 조항번호를 바꿔야 할 경우가 있습니다. 그런 경우에 활용할 수 있는 코드입니다. (주워듣기로는, 일반적인 법조문에서는 한 조항을 삭제하면 삭제된 번호를 그대로 두고 (삭제) 표시를 해놓거나, 한 조항을 추가하면 "제1조의1" 방식으로 조항번호를 매겨서 뒷 조항에 영향이 가지 않게 조치하는 것으로 알고 있습니다만, 정관은 너덜너덜하게 개정하는 일이 많지 않으니 전체 번호를 수정하는 것도 이사진 마음대로 아닐까 싶네요.)

코드 설명드리기 앞서 실행장면을 동영상으로 보여드리겠습니다. 원래는 후딱 끝나지만 디버깅 모드로 천천히 녹화해보았습니다. 자세히 보시면, 모든 조항을 1,3,5,7,~ 식으로 홀수로 맞춰놓았다가 1,2,3,4,~ 로 되돌려놓는 모습입니다.

파이썬 자동화로 조항번호 재정렬하는 모습

파이썬 자동화로 조항번호 재정렬하는 모습(움짤)

기술기준이나 규정 같은 문서는 조항 갯수만 수백개니까, 배워 볼 만한 기능 아닐까요?ㅎ 위원회도 자주 열리고, 민원에 따라 자주 개정되는 문서들도 있으니까요.

아래부터는 샘플 한/글문서와 코드 주석으로 설명을 드리겠습니다.

실행 전 문서

[예시]건축물관리법 시행령(대통령령)(제31194호)(20201204).hwp
0.14MB

실행 후 문서

0번완료.hwp
0.13MB

실행 전 문서로 아래 코드를 실행하면 "0번완료.hwp" 파일과 동일하게 바뀌는지 시도해 보시기 바랍니다.

from tkinter import Tk  # GUI 띄우는 창
from tkinter.filedialog import askopenfilename  # HWP파일 선택하기 위한 다이얼로그창
import re  # 정규표현식
import win32com.client as win32  # 한/글 열기 위한 모듈


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")


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 hwp_reindex(hwp):  # 번호 재정렬하는 메인함수
    hwp.InitScan()  # 검색준비
    조항번호 = 1  # 인덱스
    while True:  # break 만나기 전까지 무한반복~
        text = hwp.GetText()  # 문서를 엔터로 쪼갠 리스트 탐색
        if text[0] == 1:  # 종료 코드인 1이 발생하면
            break  # while문도 종료
        else:  # 그 전까지는
            if re.match(r"^제\d+조\(?", text[1]) and text[1].startswith(f"제{조항번호}조("):
                # 문단이 "제?조("로 시작하면서, 조항번호가 올바르게 들어가 있는 경우(조항순서==인덱스)
                조항번호 += 1  # 인덱스만 하나 올리고 넘어감(문서는 바뀌지 않음)
                continue
            elif re.match(r"^제\d+조\(?", text[1]) and not text[1].startswith(f"제{조항번호}조("):
                # 문단이 "제?조("로 시작하는데, 조항번호가 올바르지 않은 경우(조항순서가 인덱스와 다름)
                hwp.MovePos(201)  # moveScanPos : GetText() 로 탐색중인 현재 위치로 이동
                hwp.HAction.Run("MoveLineBegin")  # 해당라인 앞으로 이동(이 라인은 없어도 무관하나 모니터링을 위해 추가함. 
                # MovePos(201)만 실행하면 해당 캐럿으로 이동하지 않음)
                hwp_find_replace(re.match(r"^제\d+조\(?", text[1]).group(0), f"제{조항번호}조(")
                # 위에서 정의한 찾아바꾸기 함수로 "제?조(" 안의 ?를 올바른 번호로 대체함
                조항번호 = 1  # 조항번호를 1로 바꾸고
                hwp.InitScan()  # 검색을 다시 실행함(왜? 문자열 변동이 생기면 검색 강제종료됨)
            else:
                pass  # 위 탐색과정을 반복함
    hwp.ReleaseScan()  # break문을 만난 후에는 "검색종료" 코드를 실행하고
    hwp.MovePos(2)  # 완료되었으니 문서 맨 위로 이동


if __name__ == '__main__':  # 메인 파트(실행부분)
    root = Tk()  # 내장 GUI모듈 불러와서
    filename = askopenfilename()  # 파일선택창 열고, 선택한 파일명을 filename에 지정
    root.destroy()  # 파일선택창 종료
    hwp = hwp_init(filename=filename)  # 아래아한글 시작하면서 선택한 파일 열기
    hwp_reindex(hwp)  # 메인함수 실행

 

모든 주석을 다 읽지 마시고, 문맥이 이해 안 되는 부분만 유심히 읽어봐주시기 바랍니다.

궁금하시거나, 설명이 미흡한 부분이 있다면 언제든 댓글로 알려주시기 바랍니다.

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

다음은 번호 앞에 일정 길이의 공백을 추가하는 코드를 설명드리겠습니다.

행복한 하루 되세요^^


다음 포스팅은...

 

[1/4] 정관문서 서식잡기(조번호 앞 공백 추가)

1번 질문:=========================== 제[공백][공백]1조 제[공백][공백]2조 . . 제[공백]13조 제[공백]14조 등과 같이 앞에 3자리를 기준으로 빈 공백을 놓아 두고 싶습니다.... 그러면 ”조“자의 위치가 나

www.martinii.fun

 

댓글