零宽断言正则表达式替换方案

一、背景

safari浏览器不支持零宽断言正则表达式

二、解决方案

使用其他正则替换零宽断言正则(包含:(?<=)正向肯定预查、(?<!)正向否定预查、(?=)反向肯定预查、(?!)反向否定预查)

三、涉及场景

1、仅校验,不取值

如表单正则校验,如editor\src\editor\Editor\constant\todoPropsList.js:2093,此类场景可能需要重新编写(改动可能较大)正则, 无需经过zeroWidthRegPolyfill方法

 

2、校验,并取值

2.1、正则表达式不含g

如packages\editor\src\CompEvent\hooks\useFunctionValue.tsx:17,需注意改写表达式后,后续取值可能会跟随分组而变化,无需经过zeroWidthRegPolyfill方法

2.2、正则表达式含g

如editor\src\editor\Editor\components\pageDetail\CodeMirrorModal\CodeMirrorModal.jsx:87,将正则/(?<=actionMap(\['|\[|\.))[^.[\s"']+?(?==|\s|'|])/g,改为/actionMap(\['|\[|\.)([^.[\s"']+?)(=|\s|'|])+?/g ,使用zeroWidthRegPolyfill方法处理

零宽断言正则表达式替换方案

四、方法使用说明

涉及方法(主要处理正则含g的正则表达式):

packages\editor\src\utils\common.ts -> zeroWidthRegPolyfill(str, reg, n=1)

const zeroWidthRegPolyfill = (str, reg, n = 1) => {   let result = null;   const originRegStr = reg.toString();   const regStr = originRegStr.replace(/^\/(.*)+?(\/|\/g)$/, '$1');   const regWithoutG = new RegExp(regStr);   if (originRegStr.endsWith('g')) {     const arr = str.match(reg);     if (!arr) {       return result;     }     result = [];     arr.forEach((it) => {       result.push(it?.match(regWithoutG)?.[n]);     });   } else {     result = str.match(regWithoutG);   }   return result; };

1、reg含g,入参为校验字符串str、不含零宽的正则reg,以及需要真正匹配获取的字符所在的分组(必须对需要获取的字符分组)的序号n。str.match(reg)返回含目标字符的字符串数组,需要通过it?.match(regWithoutG)?.[n]二次处理,返回仅含目标字符的字符串数组

1.1、至于为啥要二次处理,这里需要知道match的使用及返回值,以下做简要对比:

 1.2、当然,使用matchAll能够一次性获取所有的匹配,并且返回带有分组信息的数组,但ie浏览器不支持

 

2、如传入不含g的表达式,则效果等同str.match(reg)。

3、使用该方法与零宽断言的区别

先看案例:

 

 可以发现,零宽实现匹配了3个结果,非零宽匹配了2个结果,这是因为“零宽正则”匹配完了之后,会从匹配到的字符开始继续匹配,预查不消耗字符

一般场景,其实是需要消耗字符的。但是,如果需要完全与零宽正则相匹配,则需要使用升级版方法。

const zeroWidthRegPolyfillPlus = (str, reg, n = 1) => {   let result = null;   const originRegStr = reg.toString();   const regStr = originRegStr.replace(/^\/(.*)+?(\/|\/g)$/, '$1');   const regWithoutG = new RegExp(regStr);   let hasMatch = true;   // 实现零宽预查补偿消耗字符,循环匹配   // const loopReg = (_str, res) => {   //   hasMatch = false;   //   const nextStr = _str.replace(regWithoutG, function () {   //     hasMatch = true;   //     res.push(arguments[n]);   //     return arguments[n + 1];   //   });   //   if (hasMatch) {   //     loopReg(nextStr, res);   //   }   // };   if (originRegStr.endsWith('g')) {     result = [];     // 实现补偿方法一:     // loopReg(str, result);     // 实现补偿方法二:     let nextStr = str;     while (hasMatch) {       hasMatch = false;       nextStr = nextStr.replace(regWithoutG, function () {         hasMatch = true;         result.push(arguments[n]);         return arguments[n + 1];       });     }   } else {     result = str.match(regWithoutG);   }   return result; };  // 目标:查找$$之间包裹的所有字符串 const str = '$abc$d$ef$'; // 1、零宽实现 const regZero = /(?<=\$).*?(?=\$)/g; let result = str.match(regZero); console.log('result1:', result); // 返回 result = ['abc', 'd', 'ef'] // 此处需要将正向肯定预查进行分组,以便方法中能够将预查的字符消耗重新补上 const reg = /\$(.*?)(\$)/g;  result = zeroWidthRegPolyfillPlus(str, reg); console.log('result2:', result); // 返回 result = ['abc', 'd', 'ef']