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

2-6. 필드속성 수정하기

by 회사원코딩 2022. 11. 9.

지난 포스팅까지 기본적인 누름틀필드와 셀필드 관련 입출력 방법을 알려드렸습니다.

이번 챕터에서는 필드를 생성하거나 속성을 수정하는 등 필드관련 기타 메서드를 소개합니다.

다소 특수한(어렵고 복잡한) 경우에 사용될 수도 있는 메서드들입니다.
이 포스팅의 메서드들은 왠만한 경우에는 사용되지 않을 것이므로
필요한 경우가 아니면 읽지 않으실 것을 권합니다.
분량도 다소 깁니다.

<목    차>

1. RenameField : 필드이름 수정하기

2. SetCurFieldName : 현재 커서위치의 필드이름 수정하기

3. SetFieldViewOption : 필드의 겉보기속성 수정(『』을 표시하지 않거나, 흰색으로 표시) - "양식모드"/"읽기모드" 전용

4. CreateField : 커서 현재 위치에 필드를 생성

5. FieldExist : 문서 내에 해당 이름의 필드가 존재하는지 여부를 검사

6. GetCurFieldName : 현재 커서가 위치하는 곳의 필드이름을 검사

7. ModifyFieldProperties : "양식모드"에서 편집 가능/불가능 여부 수정


1. 필드이름 수정하기 : hwp.RenameField

<예제파일>

누름틀필드#완성.hwp
0.02MB

벌써 몇 번째 활용하고 있는 "누름틀필드#완성.hwp" 파일입니다.

페이지마다 "이름", "성별", "생일", "취미" 등 네 개의 필드가 삽입되어 있어요.

그런데 "생일"이라는 필드명을 모두 "생년월일"로 일괄변경하고 싶다면?

아래처럼 입력하면 됩니다.

print(hwp.GetFieldList(1))
# >>> ['이름{{0}}', '성별{{0}}', '생일{{0}}', '취미{{0}}',
# ...  '이름{{1}}', '성별{{1}}', '생일{{1}}', '취미{{1}}',
# ...  '이름{{2}}', '성별{{2}}', '생일{{2}}', '취미{{2}}',
# ...  '이름{{3}}', '성별{{3}}', '생일{{3}}', '취미{{3}}',
# ...  '이름{{4}}', '성별{{4}}', '생일{{4}}', '취미{{4}}',
# ...  '이름{{5}}', '성별{{5}}', '생일{{5}}', '취미{{5}}']

hwp.RenameField("생일", "생년월일")  # 기존필드명, 신규필드명

print(hwp.GetFieldList(1))
# >>> ['이름{{0}}', '성별{{0}}', '생년월일{{0}}', '취미{{0}}',
# ...  '이름{{1}}', '성별{{1}}', '생년월일{{1}}', '취미{{1}}',
# ...  '이름{{2}}', '성별{{2}}', '생년월일{{2}}', '취미{{2}}',
# ...  '이름{{3}}', '성별{{3}}', '생년월일{{3}}', '취미{{3}}',
# ...  '이름{{4}}', '성별{{4}}', '생년월일{{4}}', '취미{{4}}',
# ...  '이름{{5}}', '성별{{5}}', '생년월일{{5}}', '취미{{5}}']

특정 순서의 필드명 하나만 변경하고 싶으면 아래처럼 인덱스번호를 붙여야 합니다.

hwp.RenameField("이름{{0}}", "name0")

"\x02" 구분자와 인덱스번호를 사용하면 동일 필드명에 한해 여러 개의 필드명을 동시에 변경하는 것도 가능합니다.

아래 코드는 성별필드 중 최초 세 개를 "성별"에서 각각 "성별0", "성별1", "성별2" 로 각각 수정합니다.

hwp.RenameField("성별{{0}}\x02성별{{1}}\x02성별{{2}}",
                "성별0\x02성별1\x02성별2")

여러 개의 필드명을 섞어 입력하면 오작동합니다. 예를 들어

hwp.RenameField("성별{{0}}\x02생일{{1}}\x02취미{{2}}", "성별0\x02생일1\x02취미2")

위 코드는 "성별{{0}}" -> "성별0", "성별{{1}}" -> "생일1", "성별{{2}}" -> "취미2"로 수정해버립니다.

RenameField는 어떤 경우에 사용할까?

저도 솔직히 현업에서 RenameField를 사용할 일은 없었습니다.

그래도 사내에 누름틀 필드가 적용된 수많은 레거시 문서가 있다면 

문서서식을 수정해야 하거나, 각기 다른 필드명을 통일해야 한다든지 하는 경우라면

RenameField를 사용하면 효율적일 것 같네요.


2. 현재 커서위치의 필드이름 수정하기 : SetCurFieldName

필드명을 수정할 때, 경우에 따라 필드명과 인덱스를 통해 수정하는 방법보다

특정 위치나 표 안의 셀로 찾아가서 직접 수정하는 방법이 훨씬 효율적일 때가 있습니다.

극단적인 케이스로, 레거시 문서에 누름틀은 들어가 있지만 누름틀 이름이 정의되어 있지 않은 경우에는

MoveToField, GetFieldList나 PutFieldText 등 필드명이 필요한 메서드를 모두 사용할 수 없으므로

탐색을 통해 직접 접근한 후에 SetCurFieldName 메서드를 통해 필드명 등 필드속성을 수정할 수 있습니다.

메서드명은 FieldName만 수정하는 것 같아 보이지만,
안내문(누름틀이 공란일 때 보이는 빨간 문구)이나 메모까지 수정 가능합니다.

예시문서는 아래와 같습니다.

필드명없음.hwp
0.02MB

누름틀은 네 개 들어가 있지만, 필드명이 정의되어 있지 않은 상태입니다.

SetCurFieldName 메서드의 사용방법은 아래와 같습니다.

hwp.SetCurFieldName(Field="이름", Direction="이름입력", memo="이름메모")

실행해보면

누름틀 안내문은 변경내용이 즉시 보이지 않습니다.
한 번 필드를 채웠다 지우거나, 저장 후 다시 불러오면 변경되어 있습니다.

그럼 위 네 개의 필드명을 각각 "이름", "생일", "성별", 취미"로 수정하는 예시코드를 보여드리겠습니다.

아래 사용된 명령어들은 이후 포스팅에서 차근차근 설명드리겠습니다.

당장 구체적으로 이해가 안 되더라도 가볍게 주석과 함께 읽어주시면 감사하겠습니다.

hwp.GetFieldList()  # 누름틀은 있는데 필드명은 하나도 없는 상태
# >>> ""

fieldname_list = ["이름", "생일", "성별", "취미"]  # 순서대로 지정할 필드명 리스트
ctrl = hwp.LastCtrl  # 제일 마지막 컨트롤에서부터 
while ctrl:  # 모든 컨트롤을 순회하면서(탐색이 문서 시작이나 문서 끝을 넘어가면 None 리턴)
    if ctrl.UserDesc == "누름틀":  # 컨트롤 이름이 "누름틀"인 경우에만
        field_name = fieldname_list.pop()  # 리스트의 마지막 원소를 field_name 변수에 지정
        hwp.SetPosBySet(ctrl.GetAnchorPos(1))  # 해당 컨트롤 위치로 커서 이동
        hwp.SetCurFieldName(Field=field_name, Direction=field_name, memo="")  # 필드명과 안내문 수정
    ctrl = ctrl.Prev  # 이전 컨트롤 선택하고 위 과정 반복

hwp.GetFieldList()  # 다시 조회해보면 네 개의 필드명이 나타남
# >>> '이름\x02생일\x02성별\x02취미'

이 메서드도 사내 특정 서식의 레거시 문서를 대량 수정하는 경우가 아니면 잘 사용되지는 않을 것 같습니다.


3. SetFieldViewOption : 필드의 겉보기속성 수정(『』을 표시하지 않거나, 흰색으로 표시) - "양식모드"/"읽기모드" 전용

이 메서드는, 일반적인 경우에 사용하지 않는 명령어입니다. 삭제예정


4. CreateField : 커서 현재 위치에 필드를 생성

일반적으로 필드를 입력하는 작업은 한/글을 열어서 직접 하는 게 효율적입니다.

그런데 굉장히 긴 문서라든지, 표의 셀 하나하나에 셀필드가 아닌 누름틀을 삽입하고 싶은 경우 등

여러 개의 누름틀 필드를 입력해야 하는 경우에는 이 CreateField 메서드가 상당시간을 단축해줄 수 있을 것입니다.

재미있는 예시를 하나 들어드리겠습니다. 적잖은 분들에게 유용할 것 같기도 합니다.

31x10 크기의 빈 표

31x10 크기의 빈 표를 생성했습니다.

먼저, 각 셀에 엑셀 워크시트 셀주소처럼 A1~J31까지 채워놓고 시작하겠습니다.

def insert_text(text):
    hwp.HAction.GetDefault("InsertText", hwp.HParameterSet.HInsertText.HSet)
    hwp.HParameterSet.HInsertText.Text = text
    hwp.HAction.Execute("InsertText", hwp.HParameterSet.HInsertText.HSet)

for row in range(1, 32):
    for col in range(65, 75):
        cell_addr = f"{chr(col)}{row}"
        insert_text(cell_addr)
        hwp.Run("MoveRight")

이제 표가 아래와 같이 채워졌습니다.

한/글 표는 엑셀과 다르게 특정 셀주소를 통해 해당 셀로 찾아가거나 원격으로 값을 입력하는 메서드가 없습니다.

엑셀의 경우에는

ws.Cells("J31").Value = "마지막 셀"

위와 같은 방식으로 아주 간편하게 원하는 위치에 값을 입력할 수 있는데 말이죠?

하지만 모든 셀에 셀주소로 필드명을 적용하면?

엑셀과 비슷하게 구동할 수 있겠죠. 아래처럼요.

hwp.PutFieldText("J31", "마지막 셀")

이런 작업을 하기 위해 모든 셀의 텍스트를 삭제하고,

셀주소를 이름으로 하는 필드를 입력해보겠습니다.

for row in range(1, 32):
    for col in range(65, 75):
        cell_addr = f"{chr(col)}{row}"
        hwp.CreateField(name=cell_addr, Direction="", memo="")
        hwp.Run("TableRightCell")

뭐가 지나갔냐 싶겠지만, 모든 셀에 필드가 생성되었습니다.

이제 아래 코드를 실행해보겠습니다.

hwp.PutFieldText("J31", "마지막 셀")

혹, '이런 엉뚱한 예제를 누가 쓰겠나?' 싶은 분도 계시겠지만,
실제로 셀주소로 찾아가기 관련 문의를 받은 적이 여러 번 있었습니다.
당시에는 셀을 일일이 탐색하면서
hwp.KeyIndicator() 가 리턴하는 셀주소 정보와 대조하는 방식으로 아쉬운대로 해결했습니다.
위 내용을 포스팅으로도 남겨놓았지만, 코드도 복잡하고 시간도 많이 걸렸습니다.
그런데, 이렇게 CreateField를 사용해서 셀주소 이름의 필드를 전부 입력해놓는 방법은
오히려 hwp.KeyIndicator 방식에 비하면 코드도 훨씬 짧고 속도도 빨라서
어떤 경우에는 더 유용할 것 같기도 합니다.

5. 해당 이름의 필드가 존재하는지 검사 : FieldExist

이 메서드는, 보다 견고한 프로그램을 짜기 위한 명령어라고 간주하셔도 좋겠습니다.

이번 챕터의 예시문서는 과기정통부 최근 보도자료입니다.

221108 즉시 (보도) 국제해킹대회 코드게이트 2022 결과.hwpx
0.19MB

올해들어 정부부처 홈페이지 보도자료를 살펴보면,

대부분 hwpx 포맷에 누름틀을 적극적으로 활용하고 있는 것 같은 모습을 보여주고 있습니다.

헤더 부분의 셀 대부분과 제목, 본문 등이 거의 모두 모두 필드로 구조화되어 있습니다.

그런데 이 문서를 비롯해 최근 업로드되는 보도자료들은 사실

지난 달까지의 보도자료와 비교하면 몇 가지 이상한 점이 있는데요...

우선 다른 자료를 먼저 보여드리겠습니다.

지난 2022년 10월 이맘때쯤의 보도자료입니다.

221012 조간(보도) 국립과천과학관 전국국공립과학관 협력 기후기술(인공지능) 원격교육 실시.hwpx
1.45MB

이 문서의 필드리스트를 확인해보면

차이점이 보이시나요?ㅎ

1. 지난 달 문서에는 필드목록 마지막에 "본문"필드가 있습니다. 최근 문서에는 "본문" 필드가 없어졌습니다.

최근 어떤 문서는 이름없는 누름틀로, 또 어떤 문서에는 누름틀조차 없더라고요..

2. 또, 최근 문서의 필드 일부에는 너저분하게 숫자가 따라붙습니다.

위에서 시키니까 필드를 형식적으로 못이겨 사용하는 냄새가 납니다..

하여튼,

과기정통부 보도자료를 크롤링으로 전부 다운받아서(1만2천 건이 조금 넘네요.)

"본문" 필드가 존재하는 경우에만 제목과 부제목, 본문 추출 작업을 하고 싶다고 가정해봅시다.

(실제로는 2022년 1월 즈음부터 필드를 적용하기 시작했습니다..)

문서를 열고 나서 바로 hwp.FieldExist로 테스트해볼 수 있겠죠.

if hwp.FieldExist("본문"):
    title = hwp.GetFieldText("제목명")
    subtitle = hwp.GetFieldText("부제목명")
    article = hwp.GetFieldText("본문")
else:
    hwp.Clear(1)

print(title)
print(subtitle)
print(article)

이런 식으로 문서 시작시에 hwp.FieldExist로 필드 존재여부를 확인해볼 수 있겠습니다.

한 가지 문제가 있습니다.

hwp.FieldExist는 정확히 일치하는 필드이름만 검사할 수 있습니다. 이 때 문제가 발생하는데

예를 들면 "담당자명" 필드를 확인하고 싶은데

실제 문서에는 "담당자명1"이나 "담당자명2"라는 필드가 있다면요?

이런 경우에는 아쉽지만 FieldExist가 무용지물이 되겠네요. (당장 과기정통부 보도자료만 봐도 그렇죠.)

이와 같은 문제는 파이썬으로 해결해야겠습니다.

아래와 같이 함수를 만들어서 활용해봅시다.

담당자가 여러 명이고, 필드명 뒤에 숫자가 붙어있어도

오류를 뱉지 않고 담당자 이름을 리스트로 리턴하는 함수입니다.

def get_field_startswith(field):
    field_list = hwp.GetFieldList(1).split("\x02")
    return [i for i in field_list if i.startswith(field)]

담당자명 = [hwp.GetFieldText(i) for i in get_field_startswith("담당자명")]

print(담당자명)
# >>> ['이웅비', '김서희']


6. 현재 커서 위치의 필드명 확인하기 : GetCurFieldName

한/글의 API 목록을 가만히 살펴보면 한/글 개발자 분들이 대단하다는 생각이 듭니다.

이 챕터에서 소개드리는 GetCurFieldName도 그런 경우인데요.

제가 왜 그렇게 생각하냐면요.

문서 안에 필드가 생성되어 있으면,

일반적으로 필드명을 통해 커서 위치를 이동하거나 값을 입력하는 경우가 대부분이거든요.

hwp.MoveToField()나 hwp.PutFieldText() 메서드를 제공하고 있으니까요.

그런데 커서를 옮겨다니거나 컨트롤을 탐색하다가 필드 컨트롤을 만났을 때

해당필드의 이름을 확인하는 정반대의 경우까지 세세히 고려해서

이런 메서드를 미리 만들어놓았다는 게 신기할 따름입니다.

서론이 길었네요..

아래는 예시문서입니다.

50x50의 빈 표를 만들었어요.

셀 안쪽 여백을 모두 0으로 없애고, 글자크기를 최소화하면 저렇게 작은 격자로 표 생성이 가능해요.

첫 번째 셀부터 색을 채우다가 "STOP"이라는 이름의 필드를 만나면 색채우기를 멈추는 작업을 해보려고 해요.

from random import randint


def cell_fill(r, g, b):
    """셀 선택 상태에서 실행해야 함
    cell_fill(255, 231, 216) 식으로
    RGB값을 세 개의 정수로 입력"""
    hwp.HAction.GetDefault("CellFill", hwp.HParameterSet.HCellBorderFill.HSet)
    hwp.HParameterSet.HCellBorderFill.FillAttr.type = hwp.BrushType("NullBrush|WinBrush")
    hwp.HParameterSet.HCellBorderFill.FillAttr.WinBrushFaceColor = hwp.RGBColor(r, g, b)
    hwp.HParameterSet.HCellBorderFill.FillAttr.WinBrushHatchColor = hwp.RGBColor(153, 153, 153)
    hwp.HParameterSet.HCellBorderFill.FillAttr.WinBrushFaceStyle = hwp.HatchStyle("None")
    hwp.HParameterSet.HCellBorderFill.FillAttr.WindowsBrush = 1
    hwp.HAction.Execute("CellFill", hwp.HParameterSet.HCellBorderFill.HSet)


while True:
    if hwp.GetCurFieldName() == "STOP":
        print("STOP")
        break
    cell_fill(randint(0, 255), randint(0, 255), randint(0, 255))
    hwp.HAction.Run("TableRightCell")

STOP이라는 필드는 아래 위치에다 매겼어요. (너무 위쪽인가요?ㅎ)

그럼 색칠 시작해볼게요.

유용한 예제는 아닌 것 같지만, 덕분에 셀에 색을 입히는 함수도 접해보셨고,

hwp.GetCurFieldName 메서드 사용법도 잘 아시게 됐을 거라고 애써 긍정적으로 생각합니다.

재미삼아 셀을 다 채워볼까요?

코드를 조금만 수정하겠습니다.

while True:  # 무한반복
    cell_fill(randint(0, 255), randint(0, 255), randint(0, 255))  # 셀에 랜덤한 색 채우기
    if "AX50" in hwp.KeyIndicator()[-1]:  # 셀주소가 AX50에 닿으면
        break  # 무한반복 종료
    hwp.HAction.Run("TableRightCell")  # 오른쪽 셀로 이동

시간이 많이 걸려서 중간생략하고

알록달록 무슨 예술작품 같기도 하네요.

코드를 조금만 더 보태면 아래와 같은 작업도 가능하답니다.


7. "양식모드"에서 편집 가능여부 수정 : ModifyFieldProperties

이 메서드도 "양식모드"라는 특수한 경우에 사용하는 명령어이므로 생략합니다.

마치며

긴 포스팅을 드디어 마쳤네요. 분량이 많아서 하나하나를 개별 포스팅으로 쪼개야 하나 하는 생각도 듭니다.

...생각만 해보았습니다. 넘어가겠습니다.

행복한 하루 되세요!

반응형

댓글0