요즘 문서 파싱의 사실상 표준 출력은 Markdown이에요. LLM이 제일 잘 읽는 형식이고, 사람 눈에도 편하니까요. 저희 한글 파서도 기본 출력이 Markdown이에요. 그런데 Markdown은 생각보다 표현력이 약한 형식이라, 변환하면서 뭔가는 꼭 잃게 돼요. 오늘은 뭘 잃고, 뭘 어떻게든 지키는지 저희 기준을 적어 볼게요.
Markdown이 못 담는 것들
한글 문서에서 자주 나오는데 Markdown으로는 그대로 표현이 안 되는 것들이에요.
| 원본 요소 | Markdown 표현 | 문제 |
|---|---|---|
| 병합 셀(colspan/rowspan) | 없음 | 표가 깨지거나 셀이 밀려요 |
| 셀 안의 여러 문단 | 없음 | 한 줄로 합쳐져요 |
| 각주·미주 | 확장 문법만 | 기본 문법엔 없어요 |
| 들여쓰기 단계(1. → 가. → 1)) | 리스트 중첩 | 한국식 번호 체계가 사라져요 |
| 글상자·그리기 개체 | 없음 | 위치 정보가 날아가요 |
| 문단 스타일 이름("조항 제목" 등) | 없음 | 서식 기반 구조 분리가 안 돼요 |
이 중에서 제일 자주 사고가 나는 게 병합 셀이에요. 공공기관 문서의 표는 거의 항상 병합이 있어요. 첫 열에 "구분"이 세로로 세 칸 병합돼 있는 식이죠.
병합 셀: 세 가지 선택지
저희가 써 본 방법은 세 가지예요.
1. 병합을 풀고 값을 복사해요. 세로로 3칸 병합된 "구분" 셀이면 세 행 모두에 "구분"을 넣어요. 표는 안 깨지고 LLM도 잘 읽어요. 단점은 원본에 없던 중복이 생기고, "이 셀은 원래 하나였다"는 정보가 사라진다는 거예요. 합계 행 같은 데서 숫자가 복사되면 LLM이 더해 버리는 사고가 나요.
2. 병합을 풀되 빈 칸으로 둬요. 중복은 없는데, 빈 칸이 "값 없음"인지 "위와 같음"인지 LLM이 못 구분해요.
3. HTML 표로 내보내요. <td rowspan="3"> 그대로요. 정보 손실이 없고 요즘 LLM은 HTML 표를 꽤 잘 읽어요. 대신 토큰이 많이 들고 사람 눈엔 불편해요.
저희 기본값은 1번(값 복사)인데, 복사된 셀을 표시해요. 구체적으로는 복사된 셀 값 뒤에 보이지 않는 마커를 붙이는 대신, Markdown과 함께 나가는 JSON 쪽에 merged_from: [r, c] 를 남겨요. Markdown만 쓰는 분은 깨끗한 표를 보고, 구조가 필요한 분은 JSON을 보시면 돼요. 숫자 셀은 예외로 복사하지 않고 빈 칸으로 둬요. 합계가 중복되는 사고를 막으려고요.
표가 너무 복잡하면(병합 셀 비율이 일정 이상이면) 자동으로 3번 HTML 표로 바꿔요. 이 임계값은 옵션으로 열어 뒀어요.
각주는 본문 끝으로 모아요
각주는 [^1] 확장 문법을 써요. 대부분의 Markdown 렌더러와 LLM이 이해해요. 다만 원본에서 각주가 어느 글자 뒤에 붙어 있었는지가 중요한 경우(법령, 계약서)가 있어서, JSON 쪽에는 각주 참조 위치를 글자 오프셋으로 남겨요.
한국식 번호 체계는 지켜요
"1. → 가. → 1) → 가)" 같은 번호 체계를 Markdown 리스트로 바꾸면 전부 1. 또는 - 가 돼서 원래 번호가 사라져요. 법령이나 규정 문서에서는 "제3조 제2항 가목"처럼 번호 자체가 참조 키라 이게 치명적이에요.
그래서 저희는 번호를 텍스트로 보존해요. 리스트 문법을 쓰지 않고 가. 신청 자격 처럼 번호를 본문에 그대로 두고, 들여쓰기 단계는 JSON의 level 필드로 남겨요. LLM 입장에서는 번호가 텍스트로 있는 게 오히려 참조하기 쉬웠어요.
스타일 이름은 헤딩으로 바꿔요
한글 문서에는 "개요 1", "조항 제목" 같은 문단 스타일이 있어요. 이게 사실상 문서의 목차 구조예요. 저희는 스타일 이름을 헤딩 레벨로 매핑하는 규칙을 문서 종류별로 갖고 있어요. 규칙에 없는 스타일은 글자 크기와 굵기로 추정하는데, 추정한 헤딩은 JSON에 inferred: true 로 표시해요. 추정인지 아닌지 모르면 나중에 아무도 못 믿으니까요.
그래서 출력은 항상 두 개예요
정리하면 저희 변환기는 Markdown 하나만 내지 않아요. 사람과 LLM이 읽는 Markdown, 구조가 필요한 시스템이 읽는 JSON을 같이 내고, 둘 다 지난주에 설명한 중간 표현(IR)에서 만들어져요. Markdown에서 어쩔 수 없이 버린 정보는 JSON에 반드시 남아 있게 하는 게 원칙이에요.
Markdown으로 바꾸면서 잃는 걸 "어쩔 수 없다"고 넘기지 않고, 어디에 남겨 둘지를 정하는 게 변환기 설계의 핵심이었어요. 다음 주에는 이렇게 뽑은 표가 얼마나 잘 뽑혔는지 재는 지표 이야기를 해 볼게요.