import { DocumentAST, DocumentBlock } from '../types/block'; import { cleanHeadingContent } from './autoNumber'; /** * blockASTToMarkdown: 将 Block AST 树无损还原为整洁的标准 Markdown 文本 */ export function blockASTToMarkdown(ast: DocumentAST): string { const chunks: string[] = []; function serializeBlock(block: DocumentBlock) { switch (block.type) { case 'heading': { const level = Math.max(1, Math.min(6, block.level || 1)); const prefix = '#'.repeat(level); const titleText = cleanHeadingContent(block.content); const numPrefix = block.numbering ? `${block.numbering} ` : ''; chunks.push(`${prefix} ${numPrefix}${titleText}\n`); break; } case 'paragraph': { if (block.content) { chunks.push(`${block.content}\n`); } break; } case 'list': { const isOrdered = block.properties?.isOrdered ?? true; const items = block.properties?.listItems || block.content .split('\n') .map((line) => line.replace(/^\s*(\d+\.|[-*+])\s+/, '').trim()) .filter(Boolean); const start = block.properties?.startNumber || 1; items.forEach((item, idx) => { const clean = item.trim(); if (!clean) return; if (isOrdered) { chunks.push(`${start + idx}. ${clean}\n`); } else { chunks.push(`- ${clean}\n`); } }); chunks.push('\n'); break; } case 'divider': { chunks.push('---\n'); break; } case 'callout': { const typeTag = block.properties?.calloutType === 'regulatory' ? '合规依据' : '提示'; chunks.push(`> **[${typeTag}]** ${block.content}\n`); break; } case 'mermaid': { chunks.push('```mermaid\n' + block.content.trim() + '\n```\n'); break; } case 'math': { chunks.push('$$\n' + block.content.trim() + '\n$$\n'); break; } case 'table': { const data = block.properties?.tableData; if (data && data.length > 0) { const colCount = Math.max(...data.map((r) => r.length)); const lines: string[] = []; // 表头行 const header = data[0]; const headerCells = Array.from({ length: colCount }, (_, idx) => header[idx] || ''); lines.push('| ' + headerCells.join(' | ') + ' |'); // 分隔线 const sep = Array.from({ length: colCount }, () => '---'); lines.push('| ' + sep.join(' | ') + ' |'); // 数据行 for (let r = 1; r < data.length; r++) { const row = data[r]; const rowCells = Array.from({ length: colCount }, (_, idx) => row[idx] || ''); lines.push('| ' + rowCells.join(' | ') + ' |'); } chunks.push(lines.join('\n') + '\n'); } break; } case 'document': { if (block.children) { for (const child of block.children) { serializeBlock(child); } } break; } default: { if (block.content) { chunks.push(`${block.content}\n`); } break; } } } serializeBlock(ast.rootBlock); return chunks.join('\n').trim() + '\n'; }