/** * 基础Markdown解析器 * 支持标题、粗体、斜体、列表、链接、图片、代码、引用等基础语法 */ class MarkdownParser { constructor() { this.patterns = { heading: /^(#{1,6})\s+(.+)$/gm, bold: /\*\*(.*?)\*\*/g, italic: /\*(.*?)\*/g, inlineCode: /`(.*?)`/g, image: /!\[(.*?)]\((.*?)\)/g, base64Image: /!\[(.*?)]\((data:image\/[^;]+;base64,[^\)]+)\)/gi, imageLink: /\[(.*?)]\((.*?\.(?:jpg|jpeg|png|gif|bmp|webp|svg))\s*(?:"(.*?)")?\)/gi, link: /\[(.*?)]\((?!.*\.(?:jpg|jpeg|png|gif|bmp|webp|svg))(.*?)\)/g, blockquote: /^>\s+(.+)$/gm, unorderedList: /^\s*[-*+]\s+(.+)$/gm, orderedList: /^\d+\.\s+(.+)$/gm, codeBlock: /```.*?\n([\s\S]*?)```/g, horizontalRule: /^---$/gm, comment: //g }; } /** * 解析Markdown文本为HTML * @param {string} markdown - Markdown文本 * @returns {string} - HTML文本 */ parse(markdown) { let html = markdown; // 处理代码块(先处理,避免其他语法干扰) html = html.replace(this.patterns.codeBlock, (match, code) => { // 保留代码内部的换行符,只去除首尾空白 let codeLines = code.split('\n'); let result = '
';
            for (let i in codeLines) {
                if (codeLines[i].trim() === '') {
                    continue;
                } else {
                    result += `${codeLines[i]}
`; } } result += '
'; return result; }); // 处理块引用 html = html.replace(this.patterns.blockquote, (match, text) => { return `
${this.parseInline(text)}
`; }); // 处理水平分割线 html = html.replace(this.patterns.horizontalRule, '
'); // 处理注释(保留HTML注释) html = html.replace(this.patterns.comment, ''); // 处理列表(先处理有序列表,再处理无序列表,避免冲突) html = this.processLists(html); // 处理段落(将连续文本包装为段落) html = this.wrapParagraphs(html); return html; } /** * 解析行内元素 * @param {string} text - 文本内容 * @returns {string} - 解析后的HTML */ parseInline(text) { let html = text; // 处理粗体 html = html.replace(this.patterns.bold, '$1'); // 处理斜体 html = html.replace(this.patterns.italic, '$1'); // 处理行内代码 html = html.replace(this.patterns.inlineCode, '$1'); // 处理普通图片格式 ![alt](url) html = html.replace(this.patterns.image, '$1'); // 处理base64图片格式 ![alt](data:image/...) html = html.replace(this.patterns.base64Image, '$1'); // 处理图片链接格式 [alt](image.jpg "title") html = html.replace(this.patterns.imageLink, (match, alt, src, title) => { const titleAttr = title ? ` title="${title}"` : ''; return `${alt}`; }); // 处理链接 html = html.replace(this.patterns.link, '$1'); return html; } /** * 处理所有列表,先识别列表类型再处理列表项 * @param {string} html - HTML文本 * @returns {string} - 处理后的HTML */ processLists(html) { // 先识别并标记列表类型 const lines = html.split('\n'); let result = ''; let currentListType = ''; let listItems = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.match(/^\d+\.\s/)) { // 有序列表项 if (currentListType !== 'ol') { // 结束前一个列表 if (listItems.length > 0) { result += this.wrapListItems(listItems, currentListType); listItems = []; } currentListType = 'ol'; } const content = line.replace(/^\d+\.\s+/, ''); listItems.push(`
  • ${this.parseInline(content)}
  • `); } else if (line.match(/^[-*+]\s/)) { // 无序列表项 if (currentListType !== 'ul') { // 结束前一个列表 if (listItems.length > 0) { result += this.wrapListItems(listItems, currentListType); listItems = []; } currentListType = 'ul'; } const content = line.replace(/^[-*+]\s+/, ''); listItems.push(`
  • ${this.parseInline(content)}
  • `); } else { // 非列表项 if (listItems.length > 0) { result += this.wrapListItems(listItems, currentListType); listItems = []; currentListType = ''; } result += lines[i] + '\n'; } } // 处理最后一个列表 if (listItems.length > 0) { result += this.wrapListItems(listItems, currentListType); } return result; } /** * 包装列表项 * @param {Array} listItems - 列表项数组 * @param {string} listType - 列表类型(ul或ol) * @returns {string} - 包装后的HTML列表 */ wrapListItems(listItems, listType) { if (listItems.length === 0) return ''; const listTag = listType === 'ol' ? 'ol' : 'ul'; return `<${listTag}>\n${listItems.join('\n')}\n\n`; } /** * 包装段落,保留Markdown原有换行 * @param {string} html - HTML文本 * @returns {string} - 包装后的HTML */ wrapParagraphs(html) { const lines = html.split('\n'); let result = ''; let currentParagraph = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); if (!trimmedLine) { // 空行,结束当前段落 if (currentParagraph) { // 检查当前段落是否已经是块级元素 if (!currentParagraph.match(/^<(h[1-6]|blockquote|ul|ol|li|pre|hr)/)) { result += `

    ${this.parseInline(currentParagraph)}

    `; } else { result += currentParagraph; } currentParagraph = ''; } result += '\n'; } else if (trimmedLine.match(/^<(h[1-6]|blockquote|ul|ol|li|pre|hr)/)) { // 已经是块级元素,直接添加 if (currentParagraph) { if (!currentParagraph.match(/^<(h[1-6]|blockquote|ul|ol|li|pre|hr)/)) { result += `

    ${this.parseInline(currentParagraph)}

    `; } else { result += currentParagraph; } currentParagraph = ''; } result += line + '\n'; } else { // 保留原有换行,不合并为一行 if (currentParagraph) { currentParagraph += '\n' + line; } else { currentParagraph = line; } } } // 处理最后一个段落 if (currentParagraph) { if (!currentParagraph.match(/^<(h[1-6]|blockquote|ul|ol|li|pre|hr)/)) { result += `

    ${this.parseInline(currentParagraph)}

    `; } else { result += currentParagraph; } } return result; } /** * HTML转义,支持保留
    标签 * @param {string} text - 原始文本 * @returns {string} - 转义后的文本 */ escapeHTML(text) { // 首先转义所有HTML特殊字符 const escapedText = text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // 然后恢复
    标签的功能 return escapedText .replace(/<br\s*\/?>/gi, '
    ') .replace(/<br\s*\/?>/gi, '
    ') .replace(/\\n/g, '
    '); // 也支持\n换行符 } /** * 专门解析表格(与MergeTableParser配合使用) * @param {string} markdown - Markdown表格文本 * @returns {string} - HTML表格 */ parseTable(markdown) { // 使用MergeTableParser处理表格 if (typeof MergeTableParser !== 'undefined') { const tableParser = new MergeTableParser(); return tableParser.parseMarkdownTable(markdown); } return markdown; } /** * 完整解析Markdown,包含表格处理 * @param {string} markdown - 完整Markdown文本 * @returns {string} - 完整HTML */ parseFull(markdown) { let html = markdown; // 先处理标题,避免表格干扰 html = html.replace(this.patterns.heading, (match, hashes, text) => { const level = hashes.length; return `${this.parseInline(text)}`; }); // 再提取并处理表格 const tableRegex = /(\|.*?\|\s*\n)+/g; html = html.replace(tableRegex, (match) => { // 检查是否是有效的表格格式 const lines = match.trim().split('\n'); if (lines.length >= 2 && lines.every(line => line.trim().startsWith('|') && line.trim().endsWith('|'))) { return this.parseTable(match); } return match; }); // 然后处理其他Markdown语法 return this.parse(html); } } // 导出为全局变量 if (typeof window !== 'undefined') { window.MarkdownParser = MarkdownParser; } // 使用示例 if (typeof module !== 'undefined' && module.exports) { module.exports = MarkdownParser; }