一、背景
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']