Add plugin md表格互转 v1.0.0#272
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new ZTools plugin, 'md-table-convert', built with Vue 3, Vite, and TypeScript to support bidirectional conversion between Markdown tables and Excel-compatible TSV format. Feedback on the implementation highlights several critical issues: the automatic downward filling of empty cells in TSV parsing can corrupt unmerged empty cells; cell parsing in Markdown tables fails to handle escaped pipes; TSV generation and Markdown conversion lack proper escaping for special characters (like pipes, tabs, newlines, and quotes); and redundant mapping operations exist in the Vue templates. Additionally, committing local configuration files with absolute paths poses a repository cleanliness issue, and exposing unused Node.js file system capabilities in the preload script introduces unnecessary security risks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // ---- 2. 合并单元格填充:空单元格向下填充 ---- | ||
| const colCount = Math.max(...rows.map((r) => r.length)) | ||
| for (let col = 0; col < colCount; col++) { | ||
| let lastValue = '' | ||
| for (let row = 0; row < rows.length; row++) { | ||
| while (rows[row].length < colCount) rows[row].push('') | ||
| const cell = rows[row][col] | ||
| if (cell !== '') { | ||
| lastValue = cell | ||
| } else if (lastValue !== '') { | ||
| rows[row][col] = lastValue | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
在解析 Excel 复制的内容时,将所有空单元格向下填充(rows[row][col] = lastValue)会导致数据错误。如果表格中原本就存在合法的空单元格(未合并),这种逻辑会错误地用上一行的值覆盖它,造成数据损坏。建议移除自动填充逻辑,仅进行列数补齐。
// ---- 2. 补齐列数 ----
const colCount = Math.max(...rows.map((r) => r.length))
for (let row = 0; row < rows.length; row++) {
while (rows[row].length < colCount) {
rows[row].push('')
}
}| // 解析单行单元格 | ||
| const parseCells = (line: string): string[] => { | ||
| // 去掉首尾可能多余的 | | ||
| let trimmed = line | ||
| if (trimmed.startsWith('|')) trimmed = trimmed.slice(1) | ||
| if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1) | ||
| return trimmed.split('|').map((cell) => cell.trim()) | ||
| } |
There was a problem hiding this comment.
使用 split('|') 解析单元格时,如果单元格内容中包含转义的竖线(例如 \|),会被错误地分割成多个单元格。建议使用负向后行断言(Negative Lookbehind)来避免分割转义的竖线,并在解析后将 \| 还原为 |。
| // 解析单行单元格 | |
| const parseCells = (line: string): string[] => { | |
| // 去掉首尾可能多余的 | | |
| let trimmed = line | |
| if (trimmed.startsWith('|')) trimmed = trimmed.slice(1) | |
| if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1) | |
| return trimmed.split('|').map((cell) => cell.trim()) | |
| } | |
| // 解析单行单元格 | |
| const parseCells = (line: string): string[] => { | |
| // 去掉首尾可能多余的 | | |
| let trimmed = line | |
| if (trimmed.startsWith('|')) trimmed = trimmed.slice(1) | |
| if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1) | |
| return trimmed.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, '|')) | |
| } |
| const markdownText = computed(() => { | ||
| if (!tableData.value) return '' | ||
| const { headers, rows } = tableData.value | ||
| const lines: string[] = [] | ||
| lines.push('| ' + headers.join(' | ') + ' |') | ||
| lines.push('| ' + headers.map(() => '---').join(' | ') + ' |') | ||
| for (const row of rows) { | ||
| const cells = headers.map((_, i) => (i < row.length ? row[i] : '')) | ||
| lines.push('| ' + cells.join(' | ') + ' |') | ||
| } | ||
| return lines.join('\n') | ||
| }) |
There was a problem hiding this comment.
在将 Excel 数据转换为 Markdown 时,如果单元格内容本身包含竖线 |,直接拼接会破坏 Markdown 表格的结构。建议在拼接前对竖线进行转义(替换为 \|)。
const markdownText = computed(() => {
if (!tableData.value) return ''
const { headers, rows } = tableData.value
const escapePipe = (str: string) => str.replace(/\|/g, '\\|')
const lines: string[] = []
lines.push('| ' + headers.map(escapePipe).join(' | ') + ' |')
lines.push('| ' + headers.map(() => '---').join(' | ') + ' |')
for (const row of rows) {
const cells = headers.map((_, i) => (i < row.length ? escapePipe(row[i]) : ''))
lines.push('| ' + cells.join(' | ') + ' |')
}
return lines.join('\n')
})
| </thead> | ||
| <tbody> | ||
| <tr v-for="(row, ri) in tableData.rows" :key="'r-' + ri"> | ||
| <td v-for="(cell, ci) in tableData.headers.map((_, idx) => (idx < row.length ? row[idx] : ''))" :key="'c-' + ri + '-' + ci">{{ cell }}</td> |
| /** 将表格数据转为 TSV 文本(可直接粘贴到 Excel) */ | ||
| function tableToTSV(data: TableData): string { | ||
| const allRows = [data.headers, ...data.rows] | ||
| return allRows.map((row) => row.join('\t')).join('\n') | ||
| } |
There was a problem hiding this comment.
在生成 TSV 文本时,如果单元格内容包含制表符 \t、换行符 \n 或双引号 ",直接用 join('\t') 拼接会导致粘贴到 Excel 时格式错乱。建议按照 TSV 标准对这些特殊字符进行双引号包裹和转义处理。
/** 将表格数据转为 TSV 文本(可直接粘贴到 Excel) */
function tableToTSV(data: TableData): string {
const escapeTSV = (val: string) => {
if (val.includes('\t') || val.includes('\n') || val.includes('"') || val.includes('\r')) {
return '"' + val.replace(/"/g, '""') + '"'
}
return val
}
const allRows = [data.headers, ...data.rows]
return allRows.map((row) => row.map(escapeTSV).join('\t')).join('\n')
}
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(npx vue-tsc *)", | ||
| "Bash(where node *)", | ||
| "Bash(where npx *)", | ||
| "Bash(winget install *)", | ||
| "Bash(mkdir -p \"E:/nodejs/node_cache\" 2>&1)", | ||
| "Bash(cmd //c dir \"E:\\\\\" 2>&1)" | ||
| ], | ||
| "additionalDirectories": [ | ||
| "E:\\nodejs" | ||
| ] | ||
| } | ||
| } |
| window.services = { | ||
| // 读文件 | ||
| readFile(file) { | ||
| return fs.readFileSync(file, { encoding: 'utf-8' }) | ||
| }, | ||
| // 文本写入到下载目录 | ||
| writeTextFile(text) { | ||
| const filePath = path.join(window.ztools.getPath('downloads'), Date.now().toString() + '.txt') | ||
| fs.writeFileSync(filePath, text, { encoding: 'utf-8' }) | ||
| return filePath | ||
| }, | ||
| // 图片写入到下载目录 | ||
| writeImageFile(base64Url) { | ||
| const matchs = /^data:image\/([a-z]{1,20});base64,/i.exec(base64Url) | ||
| if (!matchs) return | ||
| const filePath = path.join( | ||
| window.ztools.getPath('downloads'), | ||
| Date.now().toString() + '.' + matchs[1] | ||
| ) | ||
| fs.writeFileSync(filePath, base64Url.substring(matchs[0].length), { encoding: 'base64' }) | ||
| return filePath | ||
| } | ||
| } |
- Initial commit - Enhance Markdown table generation by cleaning cell content to remove line breaks
- Initial commit - Enhance Markdown table generation by cleaning cell content to remove line breaks - Remove settings.local.json and update .gitignore to include .claude directory
插件信息
本次变更
截图 / 演示
自检清单
plugins/md-table-convert/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。