// packages/core/src/utils/markdown-to-docx.ts // Phase 14.5: 마크다운 → DOCX 변환 유틸리티. // 마크다운 해석은 meeting-markdown 의 parseMarkdownBlocks 가 단일 정본이다 — // 여기서는 블록을 docx 요소로 렌더링만 한다(docx 의존은 이 서브패스에만 격리). import { Document, Packer, Paragraph, Table, TableCell, TableRow, TextRun, HeadingLevel, WidthType, } from 'docx' import { CHECKBOX_CHECKED, CHECKBOX_UNCHECKED, parseMarkdownBlocks, type MarkdownBlock, } from './meeting-markdown' const HEADING_LEVELS = { 1: HeadingLevel.HEADING_1, 2: HeadingLevel.HEADING_2, 3: HeadingLevel.HEADING_3, } as const function tableCell(text: string, bold: boolean): TableCell { return new TableCell({ children: [new Paragraph({ children: [new TextRun({ text, bold })] })], }) } /** 블록을 docx 문서 요소(문단/표)로 바꾼다. 순수 매핑이라 개수·종류로 검증할 수 있다. */ export function blocksToDocxElements(blocks: readonly MarkdownBlock[]): Array { const elements: Array = [] for (const block of blocks) { switch (block.type) { case 'heading': elements.push(new Paragraph({ text: block.text, heading: HEADING_LEVELS[block.level] })) break case 'bullet': elements.push(new Paragraph({ text: block.text, bullet: { level: 0 } })) break case 'checkbox': elements.push( new Paragraph({ text: `${block.checked ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED} ${block.text}`, bullet: { level: 0 }, }), ) break case 'paragraph': // 줄마다 문단 하나 — 한 줄짜리 항목이 한 문단으로 합쳐지지 않게 한다 for (const line of block.lines) { elements.push(new Paragraph({ children: [new TextRun(line)] })) } break case 'table': { const rows: TableRow[] = [] if (block.header.length > 0) { rows.push(new TableRow({ children: block.header.map((c) => tableCell(c, true)), tableHeader: true })) } for (const row of block.rows) { rows.push(new TableRow({ children: row.map((c) => tableCell(c, false)) })) } if (rows.length > 0) { elements.push(new Table({ rows, width: { size: 100, type: WidthType.PERCENTAGE } })) } break } case 'rule': // 구분선은 문서 흐름상 빈 문단으로 둔다 elements.push(new Paragraph({ text: '' })) break } } return elements } export async function markdownToDocx(markdown: string, title: string): Promise { const children: Array = [ new Paragraph({ text: title, heading: HeadingLevel.TITLE }), ...blocksToDocxElements(parseMarkdownBlocks(markdown)), ] const doc = new Document({ sections: [{ children }] }) return Buffer.from(await Packer.toBuffer(doc)) }