voc-validation-extract-slides.js 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. #!/usr/bin/env node
  2. // ==============================================================================
  3. // 从已生成的 HTML 报告中提取所有 slide section 的 HTML 内容
  4. // 用途:PowerShell GBK 编码损坏 chapter 文件后的快照式恢复
  5. // 输出:scripts/tools/voc-validation/_slides-snapshot.json
  6. // {
  7. // "method-1": { html: "...", tone: "green", slideNum: "03/42" },
  8. // ...
  9. // }
  10. // ==============================================================================
  11. const fs = require('fs');
  12. const path = require('path');
  13. const HTML_PATH = path.join('reports', 'jiangzhong-liver-voc-validation-report.html');
  14. const OUT_PATH = path.join('scripts', 'tools', 'voc-validation', '_slides-snapshot.json');
  15. function main() {
  16. const html = fs.readFileSync(HTML_PATH, 'utf8');
  17. // 用正则定位每个 <section class="report-section" id="..." ...>
  18. // 由于 section 里可能嵌套 section(eg. cover-hero 不是),但 report-section 不嵌套,
  19. // 采用首尾配对方式:从每个 <section class="report-section" 起点开始,寻找对应的 </section>
  20. const openRx = /<section\s+class="report-section"\s+id="([^"]+)"\s+data-tone="([^"]*)"\s+data-slide-num="([^"]+)"[^>]*>/g;
  21. const closeTag = '</section>';
  22. const starts = [];
  23. let m;
  24. while ((m = openRx.exec(html)) !== null) {
  25. starts.push({ id: m[1], tone: m[2], slideNum: m[3], start: m.index, headerEnd: m.index + m[0].length });
  26. }
  27. console.log(`Found ${starts.length} sections`);
  28. const snapshot = {};
  29. for (let i = 0; i < starts.length; i++) {
  30. const s = starts[i];
  31. // 找 section 的结束位置:从 headerEnd 开始,下一个 section 开始前最后一个 </section>
  32. const nextStart = i + 1 < starts.length ? starts[i + 1].start : html.length;
  33. const closeIdx = html.lastIndexOf(closeTag, nextStart);
  34. if (closeIdx < s.headerEnd) {
  35. console.warn(`[WARN] cannot find close for ${s.id}`);
  36. continue;
  37. }
  38. const fullHtml = html.slice(s.start, closeIdx + closeTag.length);
  39. snapshot[s.id] = {
  40. tone: s.tone,
  41. slideNum: s.slideNum,
  42. html: fullHtml,
  43. };
  44. console.log(` ${s.id.padEnd(18)} ${s.tone.padEnd(8)} ${s.slideNum} (${fullHtml.length} bytes)`);
  45. }
  46. fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
  47. fs.writeFileSync(OUT_PATH, JSON.stringify(snapshot, null, 2), 'utf8');
  48. console.log(`\n✓ 写入 ${OUT_PATH} (${Object.keys(snapshot).length} sections)`);
  49. }
  50. main();