[버그] IRCell.text — #49 수정 후 blocks는 문서 순서인데 평탄화 text는 여전히 역전
# [버그] IRCell.text — #49 수정 후 blocks는 문서 순서인데 평탄화 text는 여전히 역전
#49 수정 감사합니다. `IRCell.blocks`는 실문서에서도 문서 순서로 나오는 것을 확인했습니다.
다만 **하위 호환용 평탄화 `text`가 `blocks` 순서를 따르지 않는 경로가 남아 있습니다.** #49 답변에서 "평탄화 `text`도 문서 순서를 따르게 됐다"고 밝혀 주셨고 `markdownToHwpx` 라운드트립 재현에선 실제로 그렇게 나옵니다만, 실문서 구조(아래)에서는 `text`가 여전히 종전 순서(텍스트 먼저·표 뒤 append)입니다.
- kordoc 버전: **4.2.3**
- Node.js: v24
- 입력: 외부 파일 불필요 — 최소 HWPX 직접 구성
## 재현 (외부 파일 없이)
실문서의 구조 그대로: 최상위 표의 셀 안 한 문단의 **한 run** 에 `[표(treatAsChar=1)] 텍스트 [표(treatAsChar=1)] 텍스트`가 나란히 놓인 형태입니다.
```js
import { zipSync, strToU8 } from 'fflate';
import { parseHwpx } from 'kordoc';
const innerTbl = (id, txt) => `<hp:tbl id="${id}" zOrder="${id}" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="1" rowCnt="1" colCnt="1" cellSpacing="0" borderFillIDRef="3" noAdjust="0">
<hp:sz width="6269" widthRelTo="ABSOLUTE" height="1565" heightRelTo="ABSOLUTE" protect="0"/>
<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>
<hp:tr><hp:tc name="" header="0" hasMargin="1" protect="0" editable="0" dirty="0" borderFillIDRef="3">
<hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">
<hp:p id="0" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="0"><hp:t>${txt}</hp:t></hp:run></hp:p>
</hp:subList>
<hp:cellAddr colAddr="0" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/>
<hp:cellSz width="6269" height="1565"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/>
</hp:tc></hp:tr>
</hp:tbl>`;
const section = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hs:sec xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section">
<hp:p id="1" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="0">
<hp:tbl id="100" zOrder="1" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="1" rowCnt="1" colCnt="1" cellSpacing="0" borderFillIDRef="3" noAdjust="0">
<hp:sz width="40000" widthRelTo="ABSOLUTE" height="4000" heightRelTo="ABSOLUTE" protect="0"/>
<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>
<hp:tr><hp:tc name="" header="0" hasMargin="1" protect="0" editable="0" dirty="0" borderFillIDRef="3">
<hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">
<hp:p id="2" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="0">
${innerTbl(200, '0000-00-00')}
<hp:t> 부터 </hp:t>
${innerTbl(201, '0000-00-00')}
<hp:t> 까지</hp:t>
</hp:run>
</hp:p>
</hp:subList>
<hp:cellAddr colAddr="0" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/>
<hp:cellSz width="40000" height="4000"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/>
</hp:tc></hp:tr>
</hp:tbl>
</hp:run>
</hp:p>
</hs:sec>`;
const header = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hh:head xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head" version="1.4" secCnt="1">
<hh:refList><hh:borderFills/><hh:charProperties/><hh:paraProperties/><hh:styles/></hh:refList>
</hh:head>`;
const container = `<?xml version="1.0" encoding="UTF-8"?>
<ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container">
<ocf:rootfiles><ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/></ocf:rootfiles>
</ocf:container>`;
const hpf = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<opf:package xmlns:opf="http://www.idpf.org/2007/opf/" version="" unique-identifier="" id="">
<opf:manifest>
<opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
<opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
</opf:manifest>
<opf:spine><opf:itemref idref="header"/><opf:itemref idref="section0"/></opf:spine>
</opf:package>`;
const version = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" tagetApplication="WORDPROCESSOR" major="5" minor="1" micro="1" buildNumber="0" os="1" xmlVersion="1.4" application="Hancom Office Hangul" appVersion="11, 0, 0, 7290"/>`;
const zip = zipSync({
'mimetype': strToU8('application/hwp+zip'),
'version.xml': strToU8(version),
'META-INF/container.xml': strToU8(container),
'Contents/content.hpf': strToU8(hpf),
'Contents/header.xml': strToU8(header),
'Contents/section0.xml': strToU8(section),
});
const r = await parseHwpx(Buffer.from(zip));
const cell = r.blocks.find(b => b.type === 'table').table.cells[0][0];
console.log('cell.blocks:', JSON.stringify(cell.blocks.map(b => b.type)));
console.log('cell.text :', JSON.stringify(cell.text));
```
**결과 (4.2.3):**
```
cell.blocks: ["table","paragraph","table","paragraph"] ← 문서 순서 (#49 수정 반영 ✅)
cell.text : "부터 \n \n 까지\n0000-00-00\n0000-00-00" ← 종전 역전 순서 그대로 ❌
```
**기대:** `text`가 `blocks`의 평탄화라면
```
"0000-00-00\n부터\n0000-00-00\n까지"
```
타입 주석의 계약과도 어긋납니다:
```ts
/** blocks가 있으면 text는 blocks의 평탄화 텍스트(하위 호환용)다. */
```
## 라운드트립 재현과의 차이
#49 재현(`markdownToHwpx` 산출물)은 4.2.3에서 `text`도 문서 순서로 나옵니다(확인함):
```
"2026-01-01\n부터\n2026-12-31\n까지"
```
즉 `markdownToHwpx`가 만드는 XML 형태와 실문서 XML 형태(위 재현: 셀 문단의 **한 run** 안에 표·텍스트 혼재)가 파서의 다른 flatten 경로를 타는 것으로 보입니다. `blocks`는 두 경로 모두 고쳐졌는데 `text` 조립은 한쪽만 고쳐진 듯합니다.
## 실제 문서에서의 영향
#49에 적었던 서식 문서의 기간 입력란이 정확히 이 구조입니다. 4.2.3에서 `cell.blocks`는 `[날짜표, "부터", 날짜표, "까지"]`로 바르게 나오지만, `cell.text`는 여전히
```
"부터 까지\n0000-00-00\n0000-00-00"
```
이라, `text`를 소비하는 쪽에서는 #49 증상이 그대로 재현됩니다.
issue