/**
* 基础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');
// 处理普通图片格式 
html = html.replace(this.patterns.image, '
');
// 处理base64图片格式 
html = html.replace(this.patterns.base64Image, '
');
// 处理图片链接格式 [alt](image.jpg "title")
html = html.replace(this.patterns.imageLink, (match, alt, src, title) => {
const titleAttr = title ? ` title="${title}"` : '';
return `
`;
});
// 处理链接
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${listTag}>\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;
}