인프런 커뮤니티 질문&답변

MH H님의 프로필 이미지
MH H

작성한 질문수

직장인에게 꼭 필요한 파이썬-아래아한글 자동화 레시피

지역-학교 프로젝트 튜토리얼 개요

밑줄쳐진 단어를 괄호로 감싸는 방법이 있을까요?

해결된 질문

작성

·

591

·

수정됨

1

 아주 좋은 강의와 피드백까지 정말 감사합니다~

한글파일에 밑줄쳐진 단어가 있을때, 그 단어를 밑줄을 없애고 괄호로 감싸는 방법이 있는지 궁금합니다.

한글에서 찾아바꾸기로는 쉽지 않아보이는데, 혹시 한글에서 엑셀로 저장할 때 밑줄 단어를 괄호로 감쌀 수 있는지요.
만약 한글에서 안된다면 일단 밑줄단어를 폰트나 색깔이나 크기들을 찾아바꾸기 한후, 이 텍스트를 엑셀로 붙여넣기 할 때 원본 서식을 유지한 채 붙여넣어서 엑셀에서 다시 그 단어들을 괄호로 감쌀 수 있을까요?
예를 들면 한글파일의 밑줄단어를 찾아바꾸기에서 색깔을 빨간색으로 바꾼 후에 엑셀로 서식을 유지한채 붙여넣은 후, 엑셀에서 빨간색 단어만 찾아서 단어 앞뒤로 괄호로 감쌀 수 있을까요?

 

답변 5

2

일코님의 프로필 이미지
일코
지식공유자

[답변2/2]

 

1. 엑셀의 셀 일부 글자에만 서식 적용하는 방법 : GetCharacters

엑셀에서 서식을 직접 바꾸는 작업은 한/글에 비해 다소 복잡합니다.
엑셀에도 한/글의 좌표개념인(List, Para, Pos)와 비슷한
GetCharacters 라는 메서드가 있습니다.

예를 들어 아래 코드는 A1 셀의 값 "abcdefg" 중
3~5번 글자인 "cde"에만 밑줄을 그어주고,
끝의 "fg"는 글자를 빨간색으로 바꾸는 코드입니다.

import win32com.client as win32

excel = win32.gencache.EnsureDispatch("Excel.Application")
excel.Visible = True
wb = excel.Workbooks.Add()
ws = wb.Worksheets(1)

ws.Cells(1,1).Value = "abcdefg"
ws.Cells(1,1).GetCharacters(3, 3).Font.Underline = 2  # xlUnderlineStyleSingle
ws.Cells(1,1).GetCharacters(6, 2).Font.Color = -16776961  # Red

3, 3은 각각 Start 및 Length를 가리킵니다.
3번 글자인 c부터 3개이므로 cde에만 밑줄이 적용됩니다.
동일하게 6번 글자부터 2개인 fg에만 빨간색이 적용됩니다.

시연화면은 아래와 같습니다.

image

이제 밑줄 부분인 cde 부분 밑줄을 지우고 괄호로 감싸보겠습니다. Insert 메서드를 사용합니다.

ws.Cells(1,1).GetCharacters(3, 3).Font.Underline = 1
ws.Cells(1,1).GetCharacters(6, 0).Insert(')')
ws.Cells(1,1).GetCharacters(3, 0).Insert('(')

 

전체 시연화면은 아래와 같습니다.

image

위 내용까지 이해되셨다면,
이런 궁금증이 생길 수 있겠습니다.

'그럼 밑줄의 범위는 어떻게 자동으로 찾나?'

이건 사실 무식하게 한글자씩 순회해서 밑줄이 적용된 구간들을 찾을 수 있습니다.
(Find 등 다른 방법도 있기는 합니다ㅎㅎ)

먼저 A1셀에서 밑줄이 적용된 글자인덱스를 모두 찾아 리스트에 넣습니다.

cell = ws.Cells(1,1)

underline_list = []
for pos in range(1, len(cell.Value) + 1):
    if cell.GetCharacters(pos, 1).Font.Underline == 2:  # 밑줄이 있는 경우
        # 밑줄 있는 문자의 시작과 끝 찾기
        underline_list.append(pos)

그다음은, 간단한 알고리듬을 하나 정의합니다.
연속되는 정수구간이 있을 경우 시작점과 끝점+1을 이중리스트로 리턴하는 함수입니다.

def find_continuous_sequences(numbers):
    if not numbers:
        return []

    # 리스트 정렬
    sorted_numbers = sorted(numbers)

    # 연속 구간 찾기
    continuous_list = []
    start = sorted_numbers[0]

    for i in range(1, len(sorted_numbers)):
        # 현재 숫자가 이전 숫자와 연속적이지 않으면
        if sorted_numbers[i] != sorted_numbers[i - 1] + 1:
            # 이전 구간 추가
            continuous_list.append([start, sorted_numbers[i - 1] + 1])
            start = sorted_numbers[i]

    # 마지막 연속 구간 추가
    continuous_list.append([start, sorted_numbers[-1]+1])

    return continuous_list

# 예제 리스트
result = find_continuous_sequences(underline_list)
result.sort(reverse=True)
print(result)

# [[8, 11], [3, 6]]

이 함수에 underline_list를 넣었더니 result = [[8, 11], [3, 6]]이 리턴되었네요.

 

result의 정보를 가지고 밑줄도 지우고 괄호로 그리고 하는 전체 코드는 아래에 있습니다.

ws.Cells(1,1).Value = "abcdefghij"


# win32.constants.xlUnderlineStyleSingle == 2
ws.Cells(1,1).GetCharacters(3, 3).Font.Underline = 2
ws.Cells(1,1).GetCharacters(8, 3).Font.Underline = 2


cell = ws.Cells(1,1)
underline_list = []
for pos in range(1, len(cell.Value) + 1):
    if cell.GetCharacters(pos, 1).Font.Underline == 2:  # 밑줄이 있는 경우
        # 밑줄 있는 문자의 시작과 끝 찾기
        underline_list.append(pos)


result = find_continuous_sequences(underline_list)
result.sort(reverse=True)


for i in result:
    # 밑줄 제거
    ws.Cells(1,1).GetCharacters(i[0], i[1] - i[0]).Font.Underline = 1
    ws.Cells(1,1).GetCharacters(i[1], 0).Insert(')')
    ws.Cells(1,1).GetCharacters(i[0], 0).Insert('(')    

 

위 코드를 실행한 결과는 아래와 같습니다.

image

어때요? 밑줄 위치를 찾아서 인덱스 리스트를 만들고
해당 리스트로 서식이나 문자열 삽입을 하는 일련의 방법이
한/글과 비슷한 듯 하면서도 다른 부분이 있습니다.

 

다른 방법도 있습니다.

엑셀은 셀 전체 서식 변경은 간편한 편이지만,
셀 내부 특정 글자나, 특정 범위의 값을 변경하려고 할 때
아래아한글 못지않게 코드가 확 길어지므로 추천하는 방식은 아닙니다.
그런데 다행인 점은
한/글의 hwp.Copy와 엑셀의 ws.Paste가 (거의) 호환된다는 사실입니다.
아래 영상을 참고해 주시기 바랍니다.

 

2. hwp.Copy() / ws.Paste()를 사용하여 서식을 직접 적용하는 법

image특정 서식을 찾거나 바꾸는 건 1번 과정과 동일합니다.

 

답변이 도움이 되었으면 좋겠습니다.

추가로 궁금한 사항 있으면 또 질문 남겨주세요^^

2

일코님의 프로필 이미지
일코
지식공유자

[답변 1/2]

MH H님 안녕하세요ㅎ
질문 남겨주셔서 감사합니다.

제가 풀어가는 방식은 아래와 같습니다. (이게 좋은 방법인지는 모르겠습니다ㅜ)
예시문서는 아래와 같습니다.

 

image

  1. "찾기"기능을 스크립트매크로로 녹화합니다.
    "찾을 내용"은 무시하고, 밑줄이 있는지 여부만 찾습니다.
    이를 파이썬으로 변환 및 함수화한 코드는 아래와 같습니다.

from pyhwpx import Hwp

hwp = Hwp()


def find_underline():
    pset = hwp.HParameterSet.HFindReplace
    hwp.HAction.GetDefault("RepeatFind", pset.HSet)
    pset.FindCharShape.UnderlineType = 1  # 밑줄 찾기
    pset.IgnoreFindString = 1  # 찾을 내용은 무시
    pset.Direction = 1  # 역순
    pset.IgnoreMessage = 1  # 팝업 띄우지 않기
    return hwp.HAction.Execute("RepeatFind", pset.HSet)

 

  1. 이제 문서 끝에서부터 역순으로 위 함수를 실행하면서
    밑줄 영역의 시작/끝 좌표를 리스트에 넣습니다.

    중요한 점은, 꼭 역순으로 탐색해야 한다는 것입니다.
    왜냐면 나중에 괄호를 하나씩 추가할 때
    같은 문단 뒷쪽 글자들의 pos좌표가 기존과 달라져버리기 때문입니다.

    이 과정을 파이썬 코드로 옮기면 아래와 같습니다.

underline_wordlist = []  # 빈 리스트를 만들어놓고,
hwp.MoveDocEnd()  # 문서 끝에서부터
while find_underline():  # 한 바퀴 돌면 이 함수가 False를 리턴하므로 while문이 종료됨
    underline_wordlist.append(hwp.get_selected_pos())  # 좌표범위 저장

찾아낸 underline_wordlist는 아래처럼 생겼습니다.

image

  1. 이제 위 리스트에서 하나씩 for문으로 꺼내서(i) 아래 과정을 반복합니다.
    ① 해당 위치로 이동한 후 : hwp.select_text(i)
    ② 밑줄을 지우고 : hwp.set_font(UnderlineType=0)
    ③ "잘라내기"를 하고 : hwp.Cut()
    ④ "("를 삽입하고 : hwp.insert_text("(")
    ⑤ "붙여넣기"를 하고 : hwp.Paste()
    ⑥ 마지막으로 ")"를 삽입합니다. : hwp.insert_text(")")

     

    그래서, 전체 코드는 아래와 같고,

     

    from pyhwpx import Hwp
    
    hwp = Hwp()
    hwp.open("예시문서.hwp")
    
    
    def find_underline():
        """밑줄찾기"""
        pset = hwp.HParameterSet.HFindReplace
        hwp.HAction.GetDefault("RepeatFind", pset.HSet)
        pset.FindCharShape.UnderlineType = 1  # 밑줄 찾기
        pset.IgnoreFindString = 1  # 찾을 내용은 무시
        pset.Direction = 1  # 역순
        pset.IgnoreMessage = 1  # 팝업 띄우지 않기
        return hwp.HAction.Execute("RepeatFind", pset.HSet)
    
    
    # 밑줄범위 추출하기
    underline_wordlist = []
    hwp.MoveDocEnd()
    while find_underline():
        underline_wordlist.append(hwp.get_selected_pos())
    
    
    # 밑줄범위 찾아가서 밑줄 지우고 괄호 붙이기
    for i in underline_wordlist:
        hwp.select_text(i)
        hwp.set_font(UnderlineType=0)
        hwp.Cut()
        hwp.insert_text("(")
        hwp.Paste()
        hwp.insert_text(")")

     

  2. 시연화면은 아래와 같습니다. (insert_text 대신 Cut/Paste를 했기 때문에 서식이 유지됩니다.)

image

간단한 프로세스이고, (코드가 조금 길지는 하지만) 크게 어려운 부분은 없는 것 같습니다.
이런 패턴의 자동화 프로세스들이 많기도 하므로, 연습하시다 보면 금방 익숙해지실 겁니다.
참고로, 밑줄 안의 부분서식들을 유지하지 않아도 된다면 코드가 더 간단해질 것 같기는 합니다.

 

그리고,

엑셀로 붙여넣을 때 한/글의 서식을 유지하려면
파이썬 문자열로 가져오면 안되고,
잘라낸 텍스트 정보를 엑셀에 바로 붙여넣어야 합니다.
(다만 모든 서식이 아름답게 붙지는 않습니다..ㅜ)

답변이 너무 길어지는 관계로 2번 답변에서
한/글의 텍스트 및 서식을 엑셀에 붙여넣는 방법을 보여드리겠습니다.

MH H님의 프로필 이미지
MH H
질문자

와~
간단명료하면서도 자세하게 설명해주셔서 정말 감사합니다^^

근데 한글에서 밑줄 단어를 찾기하는 과정에서 아래 사진과 같은 팝업창이 뜨는데 이것은 안뜨게 하려면 어떻게 해야할까요?
pset.IgnoreMessage = 1 이렇게 되어있는데도 팝업창이 계속 뜨네요Img-01.27.14-004.png

 

Img-01.27.14-005.png

일코님의 프로필 이미지
일코
지식공유자

흠, 코드엔 문제가 없어 보이는 것 같은데요ㅜ,
왜 그런 오류가 생기는지 솔직히 잘 모르겠어요ㅜㅜㅜ

 

혹시 find_underline 실행시에 밑줄 범위가 선택되기는 하나요?
만약 그렇다면 find_underline 함수를 아래와 같이 바꿔보세요.

def find_underline():
    pset = hwp.HParameterSet.HFindReplace
    # hwp.HAction.GetDefault("RepeatFind", pset.HSet)  주석처리
    pset.FindCharShape.UnderlineType = 1  # 밑줄 찾기
    pset.IgnoreFindString = 1  # 찾을 내용은 무시
    pset.Direction = 1  # 역순
    pset.IgnoreMessage = 1  # 팝업 띄우지 않기
    return hwp.HAction.Execute("RepeatFind", pset.HSet)

 

밑줄 부분이 선택되지 않는 경우라면,
가지고 계신 한/글 버전?이나 문서에 맞게
코드를 수정해봐야 할 것 같아요.
한 번 해보시고 답변 남겨주세요^^

일코님의 프로필 이미지
일코
지식공유자

아이고, 잘 해결되어 다행입니다^^

올려주신 코드 저도 참고하겠습니다.

감사합니다ㅎㅎ

1

MH H님의 프로필 이미지
MH H
질문자

while문을 이렇게 수정했더니 해결 되었습니다^^

감사합니다~

    while hwp.HAction.Execute("RepeatFind", pset.HSet):
        underline_wordlist.append(hwp.get_selected_pos())
        pset.IgnoreMessage = 1  # Do not display pop-up
    return underline_wordlist

1

MH H님의 프로필 이미지
MH H
질문자

다른 이름으로 저장하는 것은 코드를 수정해서 성공했습니다. 

import os
from pyhwpx import Hwp

def process_hwp_files(folder_path):
    # Check if the folder exists
    if not os.path.exists(folder_path):
        print(f"Folder does not exist: {folder_path}")
        return

    # Iterate through files in the folder
    for filename in os.listdir(folder_path):
        file_path = os.path.join(folder_path, filename)

        # Check if it's a file with the specified extensions
        if os.path.isfile(file_path) and (filename.lower().endswith('.hwp') or filename.lower().endswith('.hwpx')):
            process_single_hwp_file(file_path)

def process_single_hwp_file(file_path):
    try:
        hwp = Hwp()
        hwp.open(file_path)

        underline_wordlist = find_underlined_words(hwp)
        pset = hwp.HParameterSet.HFindReplace
        pset.IgnoreMessage = 1  # Do not display pop-up

        for selected_range in underline_wordlist:
            hwp.select_text(selected_range)
            hwp.set_font(UnderlineType=0)  # Remove underline
            hwp.Cut()
            hwp.insert_text("( ")  
            hwp.Paste()
            hwp.insert_text(" )")
        save_path = add_suffix_to_filename(file_path, "Modified")
        hwp.save_as(save_path)
        hwp.close()

    except Exception as e:
        print(f"Error processing file '{file_path}': {e}")


def find_underlined_words(hwp):
    pset = hwp.HParameterSet.HFindReplace
    # hwp.HAction.GetDefault("RepeatFind", pset.HSet)
    pset.FindCharShape.UnderlineType = 1  # Find underline
    pset.IgnoreFindString = 1  # Ignore what to find
    pset.Direction = 1  # Reverse order
    pset.IgnoreMessage = 1  # Do not display pop-up

    underline_wordlist = []
    hwp.MoveDocEnd()

    while hwp.HAction.Execute("RepeatFind", pset.HSet):
        underline_wordlist.append(hwp.get_selected_pos())

    return underline_wordlist

def add_suffix_to_filename(file_path, suffix):
    base_path, ext = os.path.splitext(file_path)
    modified_path = f"{base_path}_{suffix}{ext}"
    return modified_path



# Example Usage
folder_to_process = r"E:\codingData\2023exam"
process_hwp_files(folder_to_process)

1

MH H님의 프로필 이미지
MH H
질문자

중간에 팝업창이 뜨는 것 빼곤 다 잘 작동합니다.
ChatGPT를 활용하여 완성한 코드를 올려봅니다. 한번 점검부탁드립니다. 특정폴더 내의 파일들을 밑줄단어를 괄호로 바꾼후에 마지막에 다른이름으로 저장하려고 했는데 그것은 성공하지 못했습니다.

MH H님의 프로필 이미지
MH H

작성한 질문수

질문하기