更新数据截止日2026.01.08
This commit is contained in:
273
static/js/markdown-plugin-table.js
Normal file
273
static/js/markdown-plugin-table.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* 支持合并单元格的Markdown表格解析器
|
||||
* 语法说明:
|
||||
* ^n# - 向上合并n个单元格
|
||||
* >n# - 向左合并n个单元格
|
||||
* ^n>m# - 同时向上合并n个单元格,向左合并m个单元格
|
||||
*/
|
||||
|
||||
class MergeTableParser {
|
||||
constructor() {
|
||||
this.patterns = {
|
||||
mergeUp: /\^(\d+)#/, // ^2#
|
||||
mergeLeft: />(\d+)#/, // >3#
|
||||
mergeBoth: /\^(\d+)>(\d+)#/, // ^2>3#
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Markdown表格并转换为HTML表格
|
||||
* @param {string} markdown - Markdown表格文本
|
||||
* @returns {string} - HTML表格
|
||||
*/
|
||||
parseMarkdownTable(markdown) {
|
||||
const lines = markdown.trim().split('\n');
|
||||
const tableData = this.parseTableLines(lines);
|
||||
return this.generateHTMLTable(tableData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表格行数据
|
||||
* @param {string[]} lines - 表格行数组
|
||||
* @returns {Object} - 表格数据对象
|
||||
*/
|
||||
parseTableLines(lines) {
|
||||
const rows = [];
|
||||
let headers = [];
|
||||
let separatorIndex = -1;
|
||||
|
||||
// 解析表头和分隔符
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith('|') && line.endsWith('|')) {
|
||||
const cells = line.slice(1, -1).split('|').map(cell => cell.trim());
|
||||
|
||||
if (line.includes('---') || line.includes('===')) {
|
||||
separatorIndex = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (separatorIndex === -1) {
|
||||
headers = cells;
|
||||
} else {
|
||||
rows.push(cells);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { headers, rows, separatorIndex };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成HTML表格
|
||||
* @param {Object} tableData - 表格数据
|
||||
* @returns {string} - HTML表格字符串
|
||||
*/
|
||||
generateHTMLTable(tableData) {
|
||||
const { headers, rows } = tableData;
|
||||
const skipMap = new Map(); // 用于标记需要跳过的单元格
|
||||
|
||||
let html = '<table border="1" style="border-collapse: collapse; width: 100%;">\n';
|
||||
|
||||
// 生成表头(支持合并单元格)
|
||||
if (headers.length > 0) {
|
||||
html += ' <thead>\n <tr>\n';
|
||||
|
||||
// 处理表头合并单元格
|
||||
headers.forEach((header, colIndex) => {
|
||||
// 检查当前单元格是否应该被跳过
|
||||
const key = `-1,${colIndex}`; // 表头行索引为-1
|
||||
if (skipMap.has(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mergeInfo = this.parseMergeSyntax(header);
|
||||
let cellHtml = '';
|
||||
|
||||
if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) {
|
||||
cellHtml = ` <th rowspan="${mergeInfo.rowspan}" colspan="${mergeInfo.colspan}">${this.escapeHTML(mergeInfo.content)}</th>\n`;
|
||||
// 标记被合并的表头单元格
|
||||
this.markMergedCells(-1, colIndex, mergeInfo, skipMap);
|
||||
} else {
|
||||
cellHtml = ` <th>${this.escapeHTML(header)}</th>\n`;
|
||||
}
|
||||
|
||||
html += cellHtml;
|
||||
});
|
||||
html += ' </tr>\n </thead>\n';
|
||||
}
|
||||
|
||||
// 生成表格主体
|
||||
html += ' <tbody>\n';
|
||||
|
||||
// 第一遍:标记所有需要合并的单元格
|
||||
rows.forEach((row, rowIndex) => {
|
||||
row.forEach((cell, colIndex) => {
|
||||
const mergeInfo = this.parseMergeSyntax(cell);
|
||||
if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) {
|
||||
this.markMergedCells(rowIndex, colIndex, mergeInfo, skipMap);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 第二遍:生成HTML,跳过被合并的单元格
|
||||
rows.forEach((row, rowIndex) => {
|
||||
html += ' <tr>\n';
|
||||
|
||||
let colOffset = 0; // 列偏移量,用于处理被跳过的单元格
|
||||
row.forEach((cell, colIndex) => {
|
||||
// 检查当前单元格是否应该被跳过
|
||||
const key = `${rowIndex},${colIndex}`;
|
||||
if (skipMap.has(key)) {
|
||||
colOffset++;
|
||||
return;
|
||||
}
|
||||
|
||||
const mergeInfo = this.parseMergeSyntax(cell);
|
||||
let cellHtml = '';
|
||||
|
||||
if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) {
|
||||
cellHtml = ` <td rowspan="${mergeInfo.rowspan}" colspan="${mergeInfo.colspan}">${this.escapeHTML(mergeInfo.content)}</td>\n`;
|
||||
} else {
|
||||
cellHtml = ` <td>${this.escapeHTML(cell)}</td>\n`;
|
||||
}
|
||||
|
||||
html += cellHtml;
|
||||
});
|
||||
html += ' </tr>\n';
|
||||
});
|
||||
html += ' </tbody>\n</table>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析合并语法
|
||||
* @param {string} cell - 单元格内容
|
||||
* @returns {Object} - 合并信息
|
||||
*/
|
||||
parseMergeSyntax(cell) {
|
||||
const result = {
|
||||
content: cell,
|
||||
rowspan: 1,
|
||||
colspan: 1,
|
||||
shouldSkip: false
|
||||
};
|
||||
|
||||
// 检查合并语法
|
||||
const bothMatch = cell.match(this.patterns.mergeBoth);
|
||||
const upMatch = cell.match(this.patterns.mergeUp);
|
||||
const leftMatch = cell.match(this.patterns.mergeLeft);
|
||||
|
||||
if (bothMatch) {
|
||||
result.rowspan = parseInt(bothMatch[1]) + 1;
|
||||
result.colspan = parseInt(bothMatch[2]) + 1;
|
||||
result.content = cell.replace(this.patterns.mergeBoth, '').trim();
|
||||
} else if (upMatch) {
|
||||
result.rowspan = parseInt(upMatch[1]) + 1;
|
||||
result.content = cell.replace(this.patterns.mergeUp, '').trim();
|
||||
} else if (leftMatch) {
|
||||
result.colspan = parseInt(leftMatch[1]) + 1;
|
||||
result.content = cell.replace(this.patterns.mergeLeft, '').trim();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记被合并的单元格
|
||||
* @param {number} rowIndex - 行索引
|
||||
* @param {number} colIndex - 列索引
|
||||
* @param {Object} mergeInfo - 合并信息
|
||||
* @param {Map} skipMap - 跳过映射
|
||||
*/
|
||||
markMergedCells(rowIndex, colIndex, mergeInfo, skipMap) {
|
||||
for (let r = 0; r < mergeInfo.rowspan; r++) {
|
||||
for (let c = 0; c < mergeInfo.colspan; c++) {
|
||||
if (r === 0 && c === 0) continue; // 跳过主单元格
|
||||
|
||||
// 处理表头合并(行索引为-1)
|
||||
const targetRow = rowIndex === -1 ? -1 : rowIndex + r;
|
||||
const key = `${targetRow},${colIndex + c}`;
|
||||
skipMap.set(key, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML转义,支持保留<br>标签
|
||||
* @param {string} text - 原始文本
|
||||
* @returns {string} - 转义后的文本
|
||||
*/
|
||||
escapeHTML(text) {
|
||||
// 首先转义所有HTML特殊字符
|
||||
const escapedText = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// 然后恢复<br>标签的功能
|
||||
return escapedText
|
||||
.replace(/<br\s*\/?>/gi, '<br>')
|
||||
.replace(/<br\s*\/?>/gi, '<br>')
|
||||
.replace(/\\n/g, '<br>'); // 也支持\n换行符
|
||||
}
|
||||
|
||||
/**
|
||||
* 将HTML表格转换回Markdown格式
|
||||
* @param {string} html - HTML表格
|
||||
* @returns {string} - Markdown表格
|
||||
*/
|
||||
htmlToMarkdown(html) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const table = doc.querySelector('table');
|
||||
|
||||
if (!table) return '';
|
||||
|
||||
let markdown = '';
|
||||
const rows = table.querySelectorAll('tr');
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const cells = row.querySelectorAll('th, td');
|
||||
let rowMarkdown = '|';
|
||||
|
||||
cells.forEach(cell => {
|
||||
const rowspan = parseInt(cell.getAttribute('rowspan') || '1');
|
||||
const colspan = parseInt(cell.getAttribute('colspan') || '1');
|
||||
let content = cell.textContent.trim();
|
||||
|
||||
// 添加合并语法
|
||||
if (rowspan > 1) {
|
||||
content += ` ^${rowspan - 1}`;
|
||||
}
|
||||
if (colspan > 1) {
|
||||
content += ` >${colspan - 1}`;
|
||||
}
|
||||
|
||||
rowMarkdown += ` ${content} |`;
|
||||
});
|
||||
|
||||
markdown += rowMarkdown + '\n';
|
||||
|
||||
// 添加分隔符行(仅在第一行后)
|
||||
if (rowIndex === 0) {
|
||||
markdown += '|' + cells.map(() => '---').join('|') + '|\n';
|
||||
}
|
||||
});
|
||||
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出为全局变量
|
||||
if (typeof window !== 'undefined') {
|
||||
window.MergeTableParser = MergeTableParser;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = MergeTableParser;
|
||||
}
|
||||
Reference in New Issue
Block a user