踩坑记——Safari与零宽断言

前两天一直在学习正则表达式,这个以前学过一下下,这次算是系统的复习,个人觉得正则表达式真的很容易忘记,在学习 ☞ 使用 ☞ 再学习 ☞ 再使用不断循环。

因为个人需求,之前整理归档笔记时用到了 Obsidian 的双链,当在 Hugo 上生成 html 时这样双链都无法正确的渲染,最后想到的解决方法就是利用 JavaScript 在客户端(也就是浏览器上)进行二次渲染,因此用到了正则表达式去匹配对应的字符串。好像又搞得有点复杂了,这次踩坑也是踩在正则表达式这里。

一开始我的想法是将无法被正确渲染的双链符号 [[…]] 直接删除的,简单、粗暴,实现起来也非常的简单,但是这个方法存在一定的缺陷,属于有舍有得吧。我知道是正则表达式没用好导致的,就决定重新复习一遍正则表达式。后来也找到了改善的方法,就是使用正则表达式中的零宽断言来排除掉以下这种渲染场景。

<code>[[…]]</code>

正则如下:

/(?<!(<code>))\[\[(.*?)\]\]/g

但没想到就是因为这个零宽断言把我坑了一把,我是真的没有想到都 3202 年了,Safari 竟然不兼容正则表达式零宽断言的某些特性。

太久没有接触正则了,记得还是几年前学习了一下正则的基础,这次算是复习。所以当代码跑不起来时我就认为是自己将正则写错了才导致不能正确地匹配到想要的值,万万没有想到这次是因为浏览器。当时自己也不知道怎么想的,明明控制台里有了报错提示,研究了半天都解决不了也没有搜索一下,那时的自己又钻了牛角尖,代码没变的情况下换了个正则就报错了,不是它搞错了还有谁?

不过现在回头想想这次踩坑对自己也是有好处的,至少让我深深体会了一把为什么学习前端除了关注代码还要关注各个浏览器兼容性。

零宽断言有四个形式:

- (?=pattern) 零宽正向先行断言 (zero-width positive lookahead assertion)
- (?! Pattern) 零宽负向先行断言 (zero-width negative lookahead assertion)
- (?<=pattern) 零宽正向后行断言 (zero-width positive lookbehind assertion)
- (?<! Pattern) 零宽负向后行断言 (zero-width negative lookbehind assertion)

发现每次搞这个正则表达式,用得最多就是打印了,在 Safari 上分别测试以上断言,如:

console.log(
  '今天有一个需求要录制屏幕,这好像是我真正头一回去研究[[Mac]]的录屏功能,之前都是截个图什么的<code>[[就满足]]</code>需求了。怎么说呢,[[Mac]]自带的QuickTime Player上截图功能已经满足我日常的使用习惯了,我也觉得十分的好用。'.match(
    /(?<!(<code>))\[\[(.*?)\]\]/g
  )
);

在 Safari 上使用零宽正向后行断言零宽负向后行断言就会提示 SyntaxError: Invalid regular expression: invalid group specifier name,但是在 Chrome 上就完全没有问题。

我的解决方案是退而求其次,使用 [^(<code>)],意思是匹配除了 <code> 以外的所有字符,与零宽负向后行断言不一样的是,前者占一个字符,后者只是条件。用上面的打印测试可以得到「“究 [[Mac]]”」与「“,[[Mac]]”」这两个值,不过这时可以将这个字符用 () 包裹起来将其变成一个组,这样取值时就可以使用 $1$2 捕获到相对应的字符组了。

既然在 Obsidian 上是双链,那么在对应的字符上替换成超链接意思意思一下吧。

const pElements = document.getElementsByTagName('p');
const regex = /([^(<code>)])\[\[(.*?)\]\]/g;
for (let i = 0; i < pElements.length; i++) {
  const elementText = pElements[i].innerHTML;
  const newText = elementText.replace(regex, '$1<a href="{{ "/tags/$2" | relURL }}"  title="查看与「$2」相关的内容">$2</a>');
  pElements[i].innerHTML = newText;
}
// Hugo默认的URL为小写,将href的大写字母转换成小写字母。
const tagLinks = document.querySelectorAll('a[href^="/tags/"]');
tagLinks.forEach((link) => {
  const href = link.getAttribute('href');
  if (href.match(/[A-Z]/)) {
    const newHref = href.toLowerCase();
    link.setAttribute('href', newHref);
  }
});

End…


查看最新方案: https://laomai.org/archives/obsidian-double-link-hugo