Claude Code로 HWP 파일 읽기·수정: 직접 해 본 결과

게시 읽는 시간 약 10분 자동화 워크플로

한글로 만든 지출 기안문(.hwp)을 Claude Code에 그대로 주고, 지출 내역 표를 정리해서 합계를 알려 달라고 했습니다. 세 번 물었는데 금액과 합계는 세 번 다 맞았어요. 그런데 한글 항목명은 “수강생 복지비”가 “○○ 운영비”가 되는 식으로 추측이었고, 문서 끝부분은 아예 보지 못했습니다.

이유는 Claude Code가 .hwp 본문을 직접 풀지 못하기 때문입니다. 파일 안에 따로 든 미리보기 텍스트만 건지는데, 이 텍스트가 앞 1,023자에서 끊겼고 한글이 깨져 있었거든요.

해 보니 넘기는 방법을 바꾸는 게 답이었습니다. 읽기만 할 거라면 한글에서 PDF로 저장해 넘기고, 한글 프로그램이 없다면 Python 스크립트로 본문을 텍스트로 뽑아 넘기고, 고쳐야 한다면 HWPX로 저장해 그 안의 XML을 고치게 하면 됩니다.

다만 HWPX를 고칠 때 합계 칸 같은 계산식은 열 때 다시 계산되지 않으니 함께 고치게 해야 해요.

2026년 10월 2~3일 Windows 11 · Windows PowerShell 5.1 테스트 환경에서 Claude Code 2.1.286과 한컴오피스 한글 2018로 직접 확인했고, Claude Code의 파일 읽기 동작과 HWP 파일 구조는 10월 3일 공식 문서로 다시 확인했습니다.

시험 문서는 표 하나가 든 1쪽짜리 지출 기안문 샘플(.hwp, 48KB)이고, 기관 이름·주소·연락처는 이 글에서 가렸습니다.

확인한 버전·환경

  • Claude Code 2.1.286 · Windows 11 · Windows PowerShell 5.1 · 확인일 2026-10-03
  • 한컴오피스 한글 2018 · Windows 11 · 확인일 2026-10-03

.hwp를 그대로 줬을 때

보낸 질문은 이겁니다.

expense.hwp에서 지출 내역 표를 행 단위로 정리하고 합계를 알려줘. 그리고 문서 맨 마지막 줄에 적힌 공개 구분 값(공개/비공개)도 알려줘.

세 번 모두 Claude Code는 파일 읽기 도구로 .hwp를 열었고, 첫 답에서 스스로 이렇게 밝혔어요.

HWP는 압축된 바이너리 파일이라 본문을 그대로 읽을 수는 없었습니다. 대신 파일 안에 들어 있는
미리보기 텍스트(PrvText)를 읽었습니다. 숫자와 영문은 정확히 읽혔지만, 한글은 깨져 있어서
문자 코드를 보고 추정한 부분이 있습니다(아래에 "추정"으로 표시).

실제 문서와 나란히 놓으면 이렇습니다(첫 회차는 마지막 줄을 묻지 않았습니다).

실제 지출 항목 1회차 2회차 3회차
시간 강의료 강의료 OOOO 관련 비용 강사료
홍보비 홍보비 (항목명 없음) (판독 불가)
소모품비 소모품·사무용품비 A4 박스 (판독 불가)
우편발송비 세부 항목 ① 첫 번째 금액 세부 항목 2건
정수기용 생수 구입 ○○ 물품 구입 구입 비용 (위 행에 합침)
수강생 복지비 ○○ 운영비 OOOO 구입 비용 (판독 불가)
합계 446,400원 맞음 맞음 맞음
마지막 줄 “비공개” — 알 수 없다고 답함 알 수 없다고 답함

금액, 곱셈식, A4·box 같은 영문은 세 번 다 정확했습니다. 한글 항목명은 “홍보비”처럼 맞힌 것도 있지만 대부분 숫자와 영문을 단서로 짐작한 말이었고, 1회차는 결재란 이름도 실제와 다르게 적었습니다. 세 번 모두 “추정”이라고 표시하긴 했지만, 그럴듯한 항목명이 표에 채워지면 대충 보는 사람은 틀린 줄 모릅니다.

글자를 못 읽어서 생긴 오해도 있었습니다. 홍보비 칸의 33,000원(400*70)*1장에서 400×70은 현수막 크기인데, 1회차와 3회차는 이걸 단가 계산식으로 읽고 “400 × 70 = 28,000인데 33,000이라 맞지 않는다”고 지적했습니다. “현수막”이라는 글자를 못 읽었으니 숫자만 보고 내린 결론이죠.

마지막 줄의 공개 구분은 “알 수 없다”고 답했는데, 짐작하지 않은 건 좋지만 이유는 문서 끝을 아예 못 봤기 때문이었습니다.

왜 앞부분만, 그것도 깨져서 읽히나

.hwp(한글 문서 파일 형식 5.0)는 여러 스트림을 하나로 묶은 Windows 복합 파일입니다. 본문과 그림은 zlib으로 압축하고, 파일 정보 같은 부분은 압축하지 않으며, PrvText 스트림에 미리보기 텍스트를 유니코드 문자열로 따로 넣어 둡니다.

파일 읽기 도구는 파일을 텍스트로 보여 주므로, 압축된 본문은 알아볼 수 없는 바이트이고 압축 안 된 미리보기만 단서로 남습니다. 미리보기는 UTF-16LE라서 영문·숫자는 사이에 0바이트가 끼어도 알아볼 수 있지만, 한글은 두 바이트가 다른 글자로 깨져 보이는 것으로 짐작됩니다. 숫자는 맞히고 한글만 틀린 결과와 들어맞습니다.

미리보기의 길이는 Python olefile로 꺼내 셌습니다.

python -c "import olefile; print(len(olefile.OleFileIO('expense.hwp').openstream('PrvText').read().decode('utf-16-le')))"
1023

1,023자였고, 미리보기는 하단 연락처의 전화번호 칸에서 끊겨 그 뒤의 팩스 번호·이메일·“비공개”가 없었습니다. 한컴 문서에는 미리보기 길이가 적혀 있지 않으니 1,023자는 이 샘플에서 잰 값입니다. 1쪽짜리도 끝이 잘렸으니, 여러 쪽짜리 보고서라면 볼 수 있는 건 첫머리뿐이라고 보는 게 안전합니다.

PowerShell 읽기 명령을 쓸 수 있게 두면 한글 깨짐만은 피합니다. 그렇게 했을 때 Claude Code는 승인이 필요 없는 Get-Content -Encoding Unicode로 미리보기를 UTF-16으로 읽어 항목명을 정확히 정리했습니다. 직접 실행하면 이렇게 나옵니다.

Get-Content .\expense.hwp -Encoding Unicode | Select-String '<연번>|<[1-5]><|<계>' | ForEach-Object { $_.Line.Trim() }
<연번><지출항목><지출내역><금액>
<1><시간 강의료><OOOO과정강사료 :김OO(시간당50,000원*1시간*1일)><50,000 ><OOOO과정강사료 :이OO(시간당50,000원*2시간*2일)><200,000 >
<2><홍보비><과정현수막제작 :33,000원(400*70)*1장><33,000 >
…
<5><수강생 복지비><OOOO과정 다과 구입><80,000 >
<계><446,400>         나. 지출내역

그래도 미리보기라 1,023자 제한은 그대로입니다. 짧은 문서를 빠르게 훑을 때만 쓸 만합니다.

읽기만 할 거라면 PDF로

제가 해 본 방법 중에는 이게 가장 정확했습니다. 한글에서 파일 → PDF로 저장하기를 한 뒤 같은 질문을 expense.pdf로 보냈더니 13초 만에 이렇게 답했습니다(연락처는 가렸습니다).

| 4 | 기타운영비 | 우편발송비 | 3,400 |
| 4 | 기타운영비 | 정수기용 생수 구입 | 20,000 |
| 5 | 수강생복지비 | OOOO과정 다과 구입 | 80,000 |
| | **계** | | **446,400** |

## 공개 구분
문서 마지막 줄(`전화 <전화번호> 전송 <팩스 번호> / <이메일> / 비공개`)에 적힌 값은 **비공개**입니다.

항목명, 금액, 문서 끝의 “비공개”까지 모두 맞았습니다.

Claude Code는 짧은 PDF는 통째로 읽고, 10쪽이 넘으면 쪽 범위를 나눠 읽습니다. 미리보기가 아니라 문서 전체를 읽으니 계약서·공문을 요약하거나 표를 옮겨 적게 할 때는 이 방법이면 충분합니다.

PDF는 고칠 수 없으니 수정은 아래 HWPX 방법을 씁니다.

한글 프로그램이 없을 때: Python으로 본문 뽑기

한컴오피스 없이 .hwp를 받았다면 압축된 본문을 직접 풀어 텍스트로 만든 뒤 넘기면 됩니다. 아래 스크립트는 olefile로 BodyText/Section* 스트림을 꺼내 zlib을 풀고, 한컴 문서가 정한 레코드 구조에서 문단 글자(HWPTAG_PARA_TEXT)만 모읍니다. 표 칸의 글자도 문단으로 저장돼 있어 함께 나옵니다.

# hwp2txt.py — 사용법: python hwp2txt.py 원본.hwp 결과.txt
import struct
import sys
import zlib

import olefile

src, dst = sys.argv[1], sys.argv[2]
ole = olefile.OleFileIO(src)
flags = struct.unpack("<I", ole.openstream("FileHeader").read()[36:40])[0]
if flags & 2 or flags & 4:
    sys.exit("암호가 걸렸거나 배포용으로 저장된 문서라 본문을 풀 수 없습니다.")
compressed = bool(flags & 1)

lines = []
sections = sorted(
    (s for s in ole.listdir() if s[0] == "BodyText"),
    key=lambda s: int(s[1].replace("Section", "")),
)
for section in sections:
    data = ole.openstream(section).read()
    if compressed:
        data = zlib.decompress(data, -15)
    i = 0
    while i + 4 <= len(data):
        header = struct.unpack("<I", data[i:i + 4])[0]
        tag, size = header & 0x3FF, (header >> 20) & 0xFFF
        i += 4
        if size == 0xFFF:
            size = struct.unpack("<I", data[i:i + 4])[0]
            i += 4
        payload, i = data[i:i + size], i + size
        if tag != 67:  # HWPTAG_PARA_TEXT: 문단 글자
            continue
        out, j = [], 0
        while j + 2 <= len(payload):
            c = struct.unpack("<H", payload[j:j + 2])[0]
            if c >= 32:
                out.append(chr(c))
                j += 2
            elif c in (0, 10, 13) or 24 <= c <= 31:  # 한 글자짜리 제어 문자
                if c == 10:
                    out.append("\n")
                j += 2
            else:  # 표·그림 같은 개체를 가리키는 8글자짜리 제어 문자
                if c == 9:
                    out.append("\t")
                j += 16
        text = "".join(out).strip()
        if text:
            lines.append(text)

with open(dst, "w", encoding="utf-8") as f:
    f.write("\n".join(lines) + "\n")
print(f"{len(lines)}개 문단을 {dst}에 저장했습니다.")

Python 3.14.3과 olefile 0.47로 실행한 결과입니다.

pip install olefile
python hwp2txt.py expense.hwp expense.txt
Successfully installed olefile-0.47
82개 문단을 expense.txt에 저장했습니다.

미리보기에 없던 팩스 번호, 이메일, “비공개”까지 82개 문단이 모두 나왔습니다. 이 expense.txt를 넘기자 Claude Code가 표 7행과 합계 446,400원, 마지막 줄의 “비공개”를 정확히 답했습니다. 표가 한 칸씩 줄로 나뉘어 나오지만 “시간 / 강의료”처럼 쪼개진 칸을 알아서 합쳐 정리했습니다.

이 스크립트는 파일 머리의 속성 비트(0번 압축, 1번 암호, 2번 배포용)를 보고 암호·배포용 문서에서는 멈춥니다.

HWP용 패키지로는 pyhwp가 있어서 먼저 써 봤습니다. pip install pyhwp로 0.1b15가 깔렸지만 Python 3.14에서는 텍스트 변환이 ModuleNotFoundError: No module named 'six'로 바로 멈췄습니다. pip install six로 넘긴 뒤에도 결과가 아쉬웠습니다.

명령 결과
hwp5txt 문장은 나오지만 표가 통째로 <표> 한 글자로 바뀜
hwp5html 지출 표와 본문 문단 대부분이 빠짐
hwp5proc xml 내용은 다 있지만 230KB 넘는 XML

이 문서의 핵심인 지출 표가 사라지거나 군더더기가 너무 많아서, 문단 글자만 뽑는 짧은 스크립트를 따로 썼습니다.

고쳐야 한다면 HWPX로

1. 한글에서 HWPX로 저장합니다

한글에서 파일 → 다른 이름으로 저장 → 파일 형식 HWPX를 고릅니다. HWPX는 한컴의 개방형 문서 형식(OWPML)으로, 실제로는 XML 파일 여러 개를 묶은 zip입니다. 저장한 expense.hwpx(83KB) 안에는 본문 Contents/section0.xml, 서식 Contents/header.xml, 미리보기 Preview/PrvText.txt, 그림 등 12개 파일이 들어 있었습니다.

2. 복사본 확장자를 .zip으로 바꿔 압축을 풉니다

Windows PowerShell 5.1의 Expand-Archive는 확장자로 파일을 거릅니다. .hwpx를 그대로 주면 .hwpx is not a supported archive file format. .zip is the only supported archive file format.이 나오니 복사본을 .zip으로 만들어 풉니다.

Copy-Item expense.hwpx expense.zip
Expand-Archive expense.zip -DestinationPath unpacked

3. Claude Code에 글자만 바꾸라고 지시합니다

압축을 푼 폴더에서 이렇게 지시했습니다.

unpacked 폴더는 expense.hwpx(한글 문서)의 압축을 푼 것이야. 지출 내역 표에서 '정수기용 생수 구입' 금액을 20,000원에서 25,000원으로 바꾸고, 합계(계)도 맞게 고쳐줘. XML 구조는 건드리지 말고 글자만 바꿔줘.

Claude Code는 검색으로 section0.xml에서 해당 칸을 찾아 편집을 네 번 했고, 41초 만에 끝났습니다. 눈여겨볼 곳은 합계 칸입니다.

합계 칸은 일반 글자가 아니라 `=SUM(?2:?8)` 계산식 필드였어요. 마지막 계산 결과가 세 군데
(`Command`, `LastResult`, 화면에 보이는 글자)에 저장돼 있어서 셋 다 451,400으로 맞췄습니다.

실제로 합계 칸에는 계산식(=SUM(?2:?8))과 마지막 계산 결과 446,400이 함께 들어 있었습니다. Claude Code는 Preview/PrvText.txt의 두 값도 고쳤고, 태그는 건드리지 않았습니다.

4. 다시 묶어 한글에서 엽니다

Claude Code는 작업을 마치며 “다시 묶을 때는 mimetype이 zip의 첫 항목이어야 하고 압축하지 않은 상태로 넣어야 한글에서 제대로 열린다”고 덧붙였습니다. 한글이 저장한 원본도 그 순서였습니다. 그래서 그 규칙을 신경 쓰지 않는 PowerShell 기본 명령으로 먼저 묶어 봤습니다.

Compress-Archive -Path unpacked\* -DestinationPath edited-a.zip
Move-Item edited-a.zip edited-a.hwpx

mimetype이 맨 앞이 아니고 압축된 상태였는데도 한글 2018에서 문제없이 열리더라고요.

한글 2018에서 연 수정본의 지출 내역 표. 정수기용 생수 구입이 25,000, 맨 아래 계가 451,400으로 바뀌어 있다
XML을 고치고 Compress-Archive로 다시 묶은 파일을 한글 2018에서 연 화면: 생수 25,000, 계 451,400

mimetype을 맨 앞에 압축 없이 넣는 Python 방식(zipfile로 mimetype만 ZIP_STORED, 나머지는 ZIP_DEFLATED)으로 묶은 파일도 열렸습니다. 여러 곳에서 열 파일이라면 원본과 같은 순서로 묶는 편이 안전합니다.

5. 합계가 맞는지 눈으로 확인합니다

합계 칸을 따로 고쳐야 하는지도 봤습니다. 생수 금액만 25,000으로 바꾸고 합계는 그대로 둔 파일을 열었더니 계는 446,400 그대로였습니다.

한글 2018에서 연 지출 내역 표. 정수기용 생수 구입은 25,000으로 바뀌었지만 맨 아래 계는 원래 값 446,400 그대로다
항목만 고친 파일: 생수는 25,000으로 바뀌었는데 계는 446,400 그대로

한글은 파일을 열 때 계산식을 다시 돌리지 않고 저장된 결과를 보여 주므로, 숫자를 고치게 할 때는 “합계도 맞게 고쳐줘”까지 지시하고 한글에서 합계를 꼭 확인하세요.

넘기기 전에 볼 것: 작성자 이름과 허용 규칙

시험 문서의 파일 속성에는 문서를 처음 만든 사람의 이름과 2004년 작성일이 남아 있었습니다. .hwp의 요약 정보에는 한글의 파일 → 문서 정보 → 문서 요약 내용이 저장되는데, 이 이름이 HWPX로 저장하면 Contents/content.hpf의 creator로, PDF로 저장하면 PDF의 작성자(Author)로 그대로 옮겨 갔습니다.

Claude Code가 읽은 내용은 모델에 전달되니, 남이 만든 문서나 회사 문서는 본문뿐 아니라 문서 정보의 이름도 확인하고 필요하면 지운 뒤 넘기세요. 회사 문서라면 외부 AI 서비스에 올려도 되는지 내부 규정부터 확인해야 합니다.

claude -p로 문서를 한꺼번에 처리하려 한다면 허용 규칙도 미리 봐 두세요. 새 폴더의 .claude/settings.json에 PowerShell(python *) 같은 허용 규칙 네 개를 넣고 -p로 실행했더니 하나도 적용되지 않고 이 경고가 떴습니다.

Ignoring 4 permissions.allow entries from .claude/settings.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects["<프로젝트 경로>"].hasTrustDialogAccepted: true in ~\.claude.json.

한 번도 대화형으로 열지 않아 “신뢰” 확인을 받지 않은 폴더였기 때문입니다. 그 폴더에서 claude를 대화형으로 한 번 실행해 신뢰를 승인하면 됩니다.

허용 규칙이 없으면 Claude Code는 우회로를 찾습니다. Python 명령이 승인 대기에 막히자 PowerShell 스크립트를 만들려 했고, 그것도 막히자 iconv 변환을 시도한 뒤 결국 미리보기 결과만으로 답하면서 olefile이나 iconv를 쓰게 허락해 달라고 했습니다.

규칙을 어디에 두는지는 Claude Code 권한 설정 글에 있습니다.

Codex CLI나 Antigravity CLI도 .hwp를 텍스트로 연다면 같은 한계에 부딪힐 가능성이 높습니다. 한글이 섞인 PowerShell 명령은 이번엔 문제가 없었지만, 출력을 변수나 파일로 받으면 깨질 수 있습니다(Claude Code·Codex 한글 깨짐 글).

마치며

제가 보기에 이번 결과에서 가장 위험한 건 틀린 답보다 그럴듯한 답이었습니다. 금액은 다 맞고 항목명만 짐작으로 채워진 표는 대충 보면 멀쩡해 보이거든요.

그래서 .hwp를 맡긴 뒤 답에 “추정”이나 “미리보기”라는 말이 보이면, 원문을 못 읽은 것으로 보고 넘기는 방법부터 바꾸시길 권합니다.

읽기는 PDF, 한글 프로그램이 없으면 Python으로 뽑은 텍스트, 수정은 HWPX. HWPX를 고친 뒤엔 합계 같은 계산식 칸을 한글에서 꼭 눈으로 확인하시고요.

지금 넘기려는 문서가 있다면 한글에서 PDF로 저장하기부터 해 보세요. 문서 작업을 자주 맡길 계획이라면 Claude Code 권한 설정 글에서 어떤 명령을 승인 없이 실행하게 둘지 정해 두면 시도만 반복하다 끝나는 일이 줄어듭니다.

참고한 공식 자료

모두 2026년 10월 3일에 확인했습니다.