verify-markdown-plain-text.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /**
  2. * 验证「markdown → 纯文本」(回答的复制按钮用的那份实现)。
  3. *
  4. * 用户 2026-09-22 反馈:复制出来的内容还带着 `###`、`**`、`[文字](链接)` 这些语法符号。
  5. *
  6. * 怎么跑(先打包):
  7. * npx esbuild harness/tools/_entry-plain-text.ts --bundle --format=esm \
  8. * --outfile=harness/tools/_plain-text.mjs --alias:@=./src --define:import.meta.env='{}'
  9. * node harness/tools/verify-markdown-plain-text.mjs
  10. */
  11. import { readFileSync } from 'node:fs';
  12. const { markdownToPlainText } = await import('./_plain-text.mjs');
  13. let pass = 0;
  14. let fail = 0;
  15. const check = (name, ok, extra = '') => {
  16. if (ok) {
  17. pass++;
  18. console.log(' ok ' + name);
  19. } else {
  20. fail++;
  21. console.log(' FAIL ' + name + ' ' + extra);
  22. }
  23. };
  24. /** markdown 语法符号:这些出现在复制结果里就是没剥干净 */
  25. const MD_SYNTAX = [/^#{1,6}\s/m, /\*\*[^*]+\*\*/, /\[[^\]]+\]\([^)]+\)/, /^\s*[-*+]\s/m, /`[^`]+`/];
  26. const hasMdSyntax = (text) => MD_SYNTAX.find((re) => re.test(text));
  27. console.log('\n【1】用户实际复制到的那份内容的形态');
  28. {
  29. const md = `### 一、市级通用查询与申兑
  30. **办理渠道**
  31. - “随申兑”惠企政策平台:https://zwdt.sh.gov.cn/qykj
  32. - “上海企业登记在线”的“中小企业公共资助项目”栏目
  33. **办理流程**
  34. 1. 登录上述平台,按行业、规模等条件匹配可申报项目。
  35. 2. 按项目要求在线提交申请。
  36. ### 四、ESG惠利贷(江苏银行上海青浦支行)
  37. - 额度与期限:0-10000万元,6-12个月。
  38. [查看产品详情](https://csjsfqeqr.yddeltazwh.org.cn/financeProduct_detail?id=317)`;
  39. const text = markdownToPlainText(md);
  40. if (process.env.DBG) console.log(JSON.stringify(text));
  41. check('不含任何 markdown 语法符号', !hasMdSyntax(text), JSON.stringify(text).slice(0, 160));
  42. check('标题只留文字(去掉 ###)', text.includes('一、市级通用查询与申兑') && !text.includes('###'));
  43. check('加粗只留文字(去掉 **)', text.includes('办理渠道') && !text.includes('**'));
  44. check('无序列表去掉 `- `,各项各占一行', text.includes('\n“随申兑”惠企政策平台:https://zwdt.sh.gov.cn/qykj\n'), JSON.stringify(text.slice(0, 120)));
  45. check('**有序列表保留序号**(顺序是内容,不是装饰)', text.includes('1. 登录上述平台') && text.includes('2. 按项目要求在线提交申请'));
  46. check(
  47. '链接写成「文字(地址)」——**地址不能丢**',
  48. text.includes('查看产品详情(https://csjsfqeqr.yddeltazwh.org.cn/financeProduct_detail?id=317)'),
  49. JSON.stringify(text.slice(-120))
  50. );
  51. check('纯文字链接保持原样', text.includes('“随申兑”惠企政策平台:https://zwdt.sh.gov.cn/qykj'));
  52. check('段落之间空一行', text.includes('一、市级通用查询与申兑\n\n办理渠道'));
  53. }
  54. console.log('\n【2】边界');
  55. {
  56. check('空串 → 空串', markdownToPlainText('') === '' && markdownToPlainText(' ') === '');
  57. check('纯文本原样返回', markdownToPlainText('就是一句话。') === '就是一句话。');
  58. check('行内代码去掉反引号', markdownToPlainText('用 `npm run dev` 启动') === '用 npm run dev 启动');
  59. check('斜体去掉星号', markdownToPlainText('这是 *重点* 内容') === '这是 重点 内容');
  60. check('引用去掉 > 符号', markdownToPlainText('> 引用一句') === '引用一句');
  61. check('分隔线不留下垃圾', markdownToPlainText('甲\n\n---\n\n乙') === '甲\n\n乙', JSON.stringify(markdownToPlainText('甲\n\n---\n\n乙')));
  62. check('data URL 图片不复制出去', !markdownToPlainText('看图 ![x](data:image/png;base64,AAAA)').includes('base64'));
  63. check('链接文字与地址相同时不重复', markdownToPlainText('[https://a.cn](https://a.cn)') === 'https://a.cn');
  64. check('多余空行被收敛', markdownToPlainText('甲\n\n\n\n乙') === '甲\n\n乙');
  65. }
  66. console.log('\n【3】拿真实回答跑一遍(DMS 里存的那一份)');
  67. {
  68. // 真实内容:前面几轮从 DMS 读回来过的那类长回答(含标题/加粗/列表/链接)
  69. const file = 'harness/tools/_real-answer.txt';
  70. let md = '';
  71. try {
  72. md = readFileSync(file, 'utf8');
  73. } catch {
  74. /* 没有就跳过 */
  75. }
  76. if (!md.trim()) {
  77. console.log(` ⏭ 没有 ${file}(把 DMS 里的 c_answer 落盘到这里即可跑这条)`);
  78. } else {
  79. const text = markdownToPlainText(md);
  80. const hit = hasMdSyntax(text);
  81. check('真实回答里没有残留的 md 语法符号', !hit, hit ? String(hit) : '');
  82. check('真实回答的文字没被吃掉(长度接近)', text.length > md.length * 0.5, `${md.length} → ${text.length}`);
  83. }
  84. }
  85. console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
  86. process.exit(fail ? 1 : 0);