Google Sheets App Script拆分HTML文章列的问题修复请求
问题:Google Apps Script拆分HTML文章时的两个问题
需求说明
通过App Script拆分HTML文章,规则如下:
- 首列存放
<h1>内容 - 第二列起依次对应第一个
<h2>及其下方所有<p>、第二个<h2>及其下方所有<p>……直至第七个<h2> - 若
<h2>数量超过7个,剩余文章内容全部放入最后一列
当前存在的问题
用现有代码实现后,碰到两个问题:
- 最后一个
<h2>(比如<h2>Conclusion</h2>)后面的<p>标签内容无法写入最后一列; - 部分文章会触发错误:
TypeError: Cannot read properties of null (reading 'match') (anonymous) @ Code.gs:44 (anonymous) @ Code.gs:43 myFunction @ Code.gs:41
现有代码
var ss = SpreadsheetApp.getActiveSpreadsheet(); var pasteSheet = [ {name: "Run Script", functionName: "myFunction"}]; ss.addMenu("Break Down Article", pasteSheet); } function myFunction() { const sheetName = "Sheet1"; // Please set the sheet name. const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); const range = sheet.getRange("A2:A" + sheet.getLastRow()); const temp1 = range.getValues().map(([a]) => a.split("\n").reduce((o, e, i, a) => { if (e) { let tagMatch = e.match(/^<(.*?)>/); let tag = tagMatch ? tagMatch[1].toUpperCase() : ""; if (tag != "P") { if (o.temp.length > 0) { o.res.push(o.temp); o.temp = []; } o.res.push([e]); } else if (i == a.length - 1) { if (tag == "P") { o.temp.push(e); o.res.push(o.temp); } else { o.res.push([e]); } } else { o.temp.push(e); } } return o; }, { res: [], temp: [] }).res ).map(e => e.map(f => f.join("\n"))); const maxLen = Math.max(...temp1.map(r => r.length)); // I modified below script. const values = temp1.map(r => [...r, ...Array(maxLen - r.length).fill(null)]); const res = values.map(r => { let n = 0; const idx = r.map(e => { let tagMatch = e.match(/^<(.*?)>/); let tag = tagMatch ? tagMatch[1].toUpperCase() : ""; return tag && tag.includes("H2") ? ++n : 0; }).indexOf(7); return [...r.splice(0, idx), r.join("\n")]; }); range.offset(0, 2, res.length, res[0].length).setValues(res); }
解决方案
问题原因分析
- TypeError错误:部分内容为空或不符合标签格式,导致
match返回null,直接访问tagMatch[1]触发报错; - 最后一个H2后的P丢失:原代码仅在处理到最后一行时才会把缓存的P加入结果,且拆分逻辑只截取到第7个H2的位置,后续的P未被正确合并到剩余内容中。
修改后的完整代码
function onOpen() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var pasteSheet = [{name: "Run Script", functionName: "myFunction"}]; ss.addMenu("Break Down Article", pasteSheet); } function myFunction() { const sheetName = "Sheet1"; const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); const range = sheet.getRange("A2:A" + sheet.getLastRow()); const temp1 = range.getValues().map(([a]) => { if (!a) return []; // 处理空单元格 return a.split("\n").reduce((o, e) => { const trimmed = e.trim(); if (!trimmed) return o; // 跳过空行 const tagMatch = trimmed.match(/^<(.*?)>/); const tag = tagMatch ? tagMatch[1].toUpperCase() : ""; if (tag === "H1" || tag === "H2") { // 遇到标题标签,先把之前缓存的P内容加入结果 if (o.temp.length > 0) { o.res.push(o.temp.join("\n")); o.temp = []; } o.res.push(trimmed); // 加入当前标题内容 } else if (tag === "P") { o.temp.push(trimmed); // 缓存段落内容 } return o; }, { res: [], temp: [] }).res; }).map(grouped => { // 处理最后剩余的段落内容 const processed = grouped.reduce((acc, item) => { if (item.startsWith("<H")) { acc.push([item]); } else { acc.length > 0 ? acc[acc.length - 1].push(item) : acc.push([item]); } return acc; }, []).map(arr => arr.join("\n")); // 检查是否有未处理的缓存段落 const temp = grouped.reduce((o, e) => { const trimmed = e.trim(); if (!trimmed) return o; const tagMatch = trimmed.match(/^<(.*?)>/); const tag = tagMatch ? tagMatch[1].toUpperCase() : ""; if (tag === "P") o.push(trimmed); return o; }, []); if (temp.length > 0 && (!processed.length || !processed[processed.length - 1].startsWith("<P"))) { processed.push(temp.join("\n")); } return processed; }); // 按需求拆分内容到对应列 const res = temp1.map(row => { let h2Count = 0; let splitPos = -1; // 找到第7个H2的位置 row.forEach((item, idx) => { if (item?.startsWith("<H2")) { h2Count++; if (h2Count === 7) splitPos = idx; } }); let result; if (splitPos !== -1) { // 截取前7个H2相关内容,剩余合并 const firstPart = row.slice(0, splitPos + 1); const remaining = row.slice(splitPos + 1).join("\n"); result = [...firstPart, remaining]; } else { result = [...row]; } // 确保首列是H1内容 const h1Idx = row.findIndex(item => item?.startsWith("<H1")); if (h1Idx !== -1) { const h1Content = row[h1Idx]; const rest = row.filter((_, i) => i !== h1Idx); result = [h1Content, ...rest]; } // 补全到8列(H1 + 7个H2组 + 剩余) while (result.length < 8) { result.push(""); } return result; }); // 写入结果到C列开始的区域 range.offset(0, 2, res.length, res[0].length).setValues(res); }
关键修改点
- 空值防护:增加对空单元格、空行的判断,避免
match返回null时触发错误; - 分组逻辑优化:每次遇到H1/H2时就把缓存的P内容归组到上一个条目,确保每个H2后面的P都被正确关联;
- 剩余内容合并:准确找到第7个H2的位置,之后的所有内容直接合并到最后一列,包括最后一个H2后的P;
- 列结构对齐:强制首列为H1内容,不足8列时自动补空,保证输出格式统一。
内容的提问来源于stack exchange,提问作者user8702720
相关产品推荐
相关产品推荐

