正则表达式regex101测试正常 脚本执行时偶发匹配失效排查
问题描述
开发Google Apps Script脚本匹配ODT文档内的指定时间文本时,使用正则/(Hora|Horario) de (cierre|finalización): (\d\d(:|.)\d\d)/i在Regex101、RegExr等正则测试平台验证,绝大多数场景可正常命中,但脚本实际运行时偶发匹配失败:
- 文本包含
Hora de cierre: 11:57 horas时无法匹配 - 格式几乎完全一致的
Hora de cierre: 13:13 horas.可正常匹配 - 手动传入完全相同的测试文本执行
match方法可正常返回结果
脚本逻辑为遍历Google Drive内的ODT格式文件,提取文档文本后执行正则匹配,手动硬编码的测试文本可正常匹配,初步怀疑故障出在Google Drive文档文本提取到正则执行的链路中。
原业务代码
function copiarResos() { let ass = SpreadsheetApp.getActiveSpreadsheet(); let maxRows = ass.getSheetByName('set_de_datos_unificado').getLastRow(); let columnaResos = ass.getSheetByName('set_de_datos_unificado').getSheetValues(2,2,maxRows,1); let columnaLinks = ass.getSheetByName('set_de_datos_unificado').getSheetValues(2,53,maxRows,1); let folder = DriveApp.getFolderById('folderID'); // 此处替换为实际文件夹ID let list = []; let files = folder.getFilesByType(MimeType.OPENDOCUMENT_TEXT); let match = ''; const ENDTIMEREGEX = /(Hora|Horario) de (cierre|finalización): (\d\d(:|.)\d\d)/i ; let testText = "3. NOTIFICAR a las partes en este acto, a las víctimas por intermedio de la Fiscalía, a la OCSPP una vez que venza el plazo para recurrir y, oportunamente, COMUNICAR. /n Hora de cierre: 11:57 horas /n PALABRAS CLAVE: resolucion_interlocutoria suspension_del_proceso_a_prueba_reanuda_plazos" while (files.hasNext()){ file = files.next(); list.push(file.getName().toString(), file.getId().toString()); } for (let i = 0; i in columnaResos; i++){ if(columnaLinks[i] == ''){ let substring = columnaResos[i]; match = list.find(element => { if (element.includes(substring)) { return true; } }); matchId = list.indexOf(match); if(matchId !== 0){ let idString = list[matchId+1]; let texto = driveTing(idString); // 加载文档文本到变量 console.log(texto) let endTimeArr = texto.match(ENDTIMEREGEX); if(endTimeArr == null){ console.log(endTimeArr) }else { let endTime = endTimeArr[0]; console.log(endTimeArr) //endTime = endTime.replace("Horario de cierre: ","").replace("Hora de finalización: ", "").replace("Hora de cierre: ", ""); //let endTimeInsert = ass.getSheetByName('set_de_datos_unificado').getRange(i+2 ,51); //endTimeInsert.setValue(endTime); } } } match = ''; }; } function driveTing(fileId) { // 该函数用于将ODT文件转为临时Google Docs,提取文本后删除临时文件 const id = Drive.Files.copy({title: "temp", mimeType: MimeType.GOOGLE_DOCS}, fileId).id; const doc = DocumentApp.openById(id); const header = doc.getHeader().getText(); const text = header + doc.getBody().getText(); DriveApp.getFileById(id).setTrashed(true) return text; }
故障根因
- 不可见字符干扰:ODT文件转存为Google Docs的过程中,部分文档的冒号、数字、空格位置会混入零宽不可见字符(常见为零宽空格
U+200B、字节顺序标记U+FEFF、软连字符U+00AD)。这类字符肉眼完全不可见,手动复制测试文本时不会被带入,但通过API提取的原始文本中会完整保留,直接打断正则的连续字符匹配逻辑,造成漏匹配。 - 正则容错性不足:原正则中分隔小时和分钟的
.属于正则元字符,未做转义,理论上可匹配任意单字符,遇到特殊字符组合时可能出现非预期匹配;同时正则默认关键词、冒号、时间之间只能有单个半角空格,无法兼容多空格、制表符、不可见空白的场景。 - 隐藏代码bug:
driveTing函数中直接调用doc.getHeader().getText(),如果目标文档没有页眉,getHeader()会返回null,直接调用方法会抛出脚本错误。
修复方案
- 增加文本清洗逻辑:在拿到提取的文档文本、执行正则匹配前,先移除所有零宽不可见字符,统一异常标点:
let texto = driveTing(idString); // 移除所有零宽不可见字符、软连字符 texto = texto.replace(/[\u200B-\u200D\uFEFF\u00AD]/g, ''); // 统一全角/半角冒号,统一时间分隔符为半角冒号 texto = texto.replace(/[::]/g, ':').replace(/(\d{1,2})[..](\d{2})/g, '$1:$2'); - 优化正则表达式:修正元字符问题,增加空白字符兼容,简化捕获分组:
优化点说明:const ENDTIMEREGEX = /(?:Hora|Horario)\s+de\s+(?:cierre|finalización)\s*:\s*(\d{1,2}:\d{2})/i;- 不需要单独提取的分组改为非捕获分组
(?:),减少冗余返回项 - 关键词之间的单个空格替换为
\s+,兼容多空格、制表符等空白场景 - 冒号前后增加
\s*匹配,兼容冒号前后误加空格的情况 - 修正原正则未转义
.的问题,明确匹配时间分隔符为冒号,避免非预期匹配 - 直接捕获
小时:分钟格式的时间值,后续不需要额外做多轮字符串替换
- 不需要单独提取的分组改为非捕获分组
- 补全页眉空值兼容:修改
driveTing函数,处理无页眉的文档场景:function driveTing(fileId) { const id = Drive.Files.copy({title: "temp", mimeType: MimeType.GOOGLE_DOCS}, fileId).id; const doc = DocumentApp.openById(id); const header = doc.getHeader() ? doc.getHeader().getText() : ''; const text = header + doc.getBody().getText(); DriveApp.getFileById(id).setTrashed(true) return text; }
内容的提问来源于stack exchange,提问作者IFritzler
相关产品推荐
相关产品推荐

