왜 SuperDoc이 마지막 이미지만 표시하는가: DOCX 내부 구조 조사
Source: Dev.to
증상
생성된 보고서에 여러 개의 속성 이미지가 그리드 형태로 배열되어 있었습니다.
| 편집기 내부 예상 | SuperDoc 내부 실제 결과 |
|---|---|
| 이미지 1 → 슬롯 1 | 슬롯 1 → 마지막 이미지 |
| 이미지 2 → 슬롯 2 | 슬롯 2 → 마지막 이미지 |
| 이미지 3 → 슬롯 3 | 슬롯 3 → 마지막 이미지 |
이상하게도:
- Microsoft Word → 정상
- LibreOffice → 정상
- Google Docs → 정상
- SuperDoc 편집기 → 오류
따라서 DOCX 자체는 유효했으며, 문제는 편집기별로 나타났습니다.
환경
| 도구 | 버전 |
|---|---|
| SuperDoc | 1.16.x |
docx-templates | 4.15.0 |
| Node | 20.x |
이미지는 docx-templates 루프를 사용하여 삽입되었으며 docxUrl을 통해 편집기에 로드되었습니다.
첫 번째 확인: DOCX가 손상되었나요?
Initial assumption: the DOCX generation might be broken.
Verification steps
- Opened in Word – ✅
- Opened in LibreOffice – ✅
- Converted to PDF – ✅
- Uploaded to Google Docs – ✅
Everything rendered correctly everywhere except inside the editor.
Conclusion: basic DOCX corruption ruled out.
Source: …
Deep‑Debugging Approach
DOCX 파일은 단순히 ZIP 아카이브이기 때문에, 원시 XML을 추출하여 검사했습니다:
word/document.xml
word/_rels/document.xml.rels
word/media/
목표는 이미지가 내부적으로 어떻게 참조되는지 확인하는 것이었습니다.
Finding 1: Non‑Standard Relationship IDs
보통 DOCX는 rId1, rId2, rId3 같은 관계 ID를 사용합니다.
생성된 DOCX에는 다음과 같은 해시 기반 ID가 포함되어 있었습니다:
수행한 테스트 – 모든 관계 ID를 표준 rId1, rId2, … 로 변환하고 참조를 업데이트했습니다.
결과: 편집기 동작에 변화가 없었으며, 모든 이미지 슬롯이 여전히 마지막 이미지를 표시했습니다.
결론: 관계 ID가 주요 원인은 아니었습니다.
Finding 2: Media Filenames
생성된 파일 이름은 다음과 같은 형태였습니다:
template_document.xml_img2073076884.jpg
일반적인 image1.jpg, image2.jpg 형태가 아니라는 점이었습니다.
수행한 테스트 – 파일 이름을 표준 형식으로 바꾸고 참조를 업데이트했습니다.
결과: 편집기에서 여전히 문제가 발생했습니다.
결론: 파일 이름 규칙이 원인이 아니었습니다.
Finding 3: Images Inside Tables?
이미지가 표 셀 안에 들어가면서 평탄화되거나 병합되는 것이 의심되었습니다.
점검: 원시 XML 구조 – 이미지가 이미 독립적인 단락에 있었으며, 복잡한 표 그리기 구조 안에 중첩되어 있지 않았습니다.
결과: 구조적인 문제는 발견되지 않았습니다.
Finding 4: Base64 External References
편집기 내부에서 미디어 파일 해석 문제를 배제하기 위해 이미지를 base64‑인코딩된 외부 참조로 변환했습니다.
결과: 여전히 문제가 지속되었으며, 모든 슬롯이 마지막 이미지를 계속 표시했습니다.
결론: 문제는 이미지가 저장되거나 참조되는 방식이 아니라, 편집기가 drawing‑node의 정체성을 해석하는 방식에 있었습니다.
지금까지 가장 강력한 단서 (미확인)
각 이미지의 drawing XML을 검사하던 중, 일정한 패턴이 드러났습니다:
모든 이미지에 id="0"이 들어 있었습니다.
OOXML 사양에 따르면, 이 속성은 문서 내 그림 객체당 고유해야 합니다.
docx-templates가 생성된 모든 이미지에 id="0"을 할당하는 것으로 보입니다.
작동 가설: SuperDoc은 렌더링에 ProseMirror(또는 유사 엔진)를 사용합니다. 파서가 pic:cNvPr/@id를 사용해 drawing 노드를 구분한다면, 중복된 ID 때문에 모든 이미지를 동일한 노드로 인식하고 마지막에 로드된 이미지가 각 슬롯을 “점령”할 수 있습니다.
참고: 이는 가설일 뿐이며, 아직 확인된 해결책은 아닙니다. 다음 단계는 docx-templates 출력에 고유한 순차 ID를 삽입하도록 패치하고, 서버를 깨끗이 재시작한 뒤 결과를 검증하는 것입니다. 이전에 실패한 테스트는 서버를 재빌드 후 재시작하지 않아 오래된 컴파일 코드를 대상으로 했던 것이 원인이었습니다.
지금까지 시도한 내용
| 시도 | 결과 |
|---|---|
| 표에서 이미지 추출 | 변화 없음 |
관계 ID를 rId1/rId2 로 이름 변경 | 변화 없음 |
미디어 파일 이름을 image1.jpg 로 변경 | 변화 없음 |
| 이미지를 base64 외부 참조로 변환 | 변화 없음 |
| 드로잉 XML 검사 | 모든 이미지에서 중복 id="0" 발견 |
| 고유 ID로 패치 | 아직 완전히 검증되지 않음 |
현재 상태
문제는 아직 조사 중입니다.
주요 가설: 모든 이미지에서 pic:cNvPr/@id="0"이 중복되어 편집기가 이를 하나의 노드로 병합합니다.
다음 단계
- drawing XML에 고유 ID를 삽입합니다.
- 서버를 완전히 재시작합니다.
- 깨끗한 검증을 수행합니다 (DOCX를 SuperDoc, Word, LibreOffice, Google Docs에서 열어 확인).
디버깅 세션에서 얻은 교훈
-
Word에서 동작한다 해도 여전히 잘못될 수 있다
Word는 많은 OOXML 위반을 조용히 허용한다. 편집기와 변환기는 더 엄격하다. Word 렌더링이 올바름과 동일하다고 가정하지 말라. -
DOCX를 내부적으로 항상 검사하라
DOCX를 ZIP 파일로 취급한다. 압축을 풀고 XML을 검사하고 패턴을 검색하며 구조를 확인하면 디버깅이 훨씬 빨라진다. -
렌더링 엔진은 다르게 동작한다
| Tool | Strictness |
|---|---|
| Word | 매우 관대함 |
| Google Docs | 보통 |
| LibreOffice | 엄격함 |
| Editor engines | 매우 엄격함 |
편집기별 버그는 종종 사양이 요구하지만 Word가 조용히 무시하는 구조적 가정에서 비롯된다.
왜 이걸 문서화하는가
버그는 아직 해결되지 않았지만 디버깅 과정에서 가능한 근본 원인(중복 pic:cNvPr/@id)을 발견했습니다. 이 과정을 공유하면 비슷한 편집기‑특정 렌더링 문제를 겪는 사람들에게 도움이 될 수 있고, “워드에서 보기엔 괜찮다”는 수준을 넘어 DOCX 출력물을 검증해야 한다는 점을 상기시켜 줍니다.
DOCX 이미지 렌더링에서 비슷한 현상을 겪으셨다면, 어떤 결과를 얻었는지 알려주시면 정말 감사하겠습니다.