IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    如何快速解析HTML里的WikiLink?

    @1900\'Blog发表于 2024-09-12 16:59:50
    love 0

    前几天在 你好Astro! 中提到想把Obsidian的文章集成在博客中来,并实现CMS和Obsidian之间通过Wikilink的互相引用。

    不过因为Ghost的API只提供了文章的HTML字符串,Remark衍生的WikiLink插件肯定都是无法使用的,而且目前Github上能找到的WikiLink相关的案例基本上都是整对于Markdown语法去解析的,对于我来说基本上没什么参考价值。

    所以我只能走另外的路子了,不过方法无非就是以下几种

    • 正则过滤替换
    • 生成虚拟DOM处理

    正则过滤

    昨天晚上我倒是找到了 loveminimal 大佬写的一个姑且能用方案,但是这段代码有个不大不小的缺陷,就是正则会将所有标签内的 [[]] 内容都进行解析转换,所以 pre 代码块内的内容也会被解析成Wikilink,这意味着会无法控制的出现在任何你可能不想他出现的地方,所以目前这种方案暂时待选。

    const wikilinks = (innerHtml) => {
        let _innerHtml = innerHtml;
        if (_innerHtml.indexOf('[[') > -1) {
            let _re = /!\[\[(([\/\-\.\*\$\&]|\w|\s|[^\x00-\xff])*\.\w+)\s*\|?\s*(\d*)\]\]/g;
            let _str = _innerHtml.replace(_re, '<img src="/$1" alt="$1" width="$3" />');
    
    
            // 2. 后匹配替换链接
            let _reLink = /\[\[(([\/\-\.\*\$\:\#]|\w|\s|[^\x00-\xff])*)\|?(([\/\-\.\*\$]|\w|\s|[^\x00-\xff])*)\]\]/g;
            // let _strLink = _str.match(_reLink);
            // let _strLink = _str.replace(_reLink, '<a href="$1">$3</a>');
            let _strLink = _str.replace(_reLink, (val) => {
                val = val.replace(/[\[\]]/g, '');
                let _arr = val.split(/\s*\|\s*/);
                let _relLink = _arr[0];
                let _desc = _arr[1] ? _arr[1] : _arr[0];
    
                // 检查链接描述是否包含 #锚点,形式有(我们假设当前文章名称为 test ,它有一个章节 ttt):
                // - 2.1. 孙子兵法#军争篇 - 此类可以正常识别
                // - 2.2. cpu-是如何制造出来的#18.-等级测试 - https://example.com/cpu-是如何制造出来的#18.-等级测试 ,
                //        此类锚点中包含特殊符号 `.` ,在新标签中打开,且无法正确定位到锚点
                // - 2.3 test#ttt - https://example.com/test#ttt 默认会在新标签页中打开,需要优化为在当前页面滚动
                // - 2.4  #ttt - 不能正常,会翻译为 https://example.com/#ttt ,丢失了当前页面路径
                let _idx = _desc.indexOf('#');
                if (_idx > -1) {
                    // 2.4
                    if (_idx == 0) {
                        _relLink = location.pathname.slice(1) + _desc;
                    } else {
                        // 2.3
                        _relLink = _desc.replace('#', '/#');
    
                        // 2.2
                        _relLink = _relLink.replace(/[\.\、]/g, '');
                    }
                }
    
                // console.log(_arr);
                // console.log(_desc);
                // return `<a href="${_arr[0]}">${_desc}</a>`
                return `<a href="/${_relLink.replace(/\s/g, '-').toLowerCase()}">${_desc}</a>`;
                // });
            });
    
            return _strLink;
        }
    };

    虚拟化DOM

    这个方式就是在Nodejs里将HTML字符串虚拟化成DOM,再去对DOM进行操作,虽然理论上可行的,但是效率应该是极低的,这个我是不愿意使用的,所以直接排除掉了。

    没有其他方案了吗?

    真的没有其他方案了吗?

    我今天这样想着,抱着试一试的心态换着各种关键字在Github上搜索,运气不错的让我找到了这个个库:html-parse-stringify ,他可以将html字符串快速抽象成一个AST语法树,我的想法是通过遍历这个语法树来找出文本内容,再反序列化成html字符串不就好了?

    所以我试着按着我的思路写了个Demo,好像的确可行,而且还可以配置那些情况下包含的 [[]] 不进行渲染。

    
    import HTML from 'html-parse-stringify';
    var ast = HTML.parse(page?.html); // [!code highlight]
    let fatherList = '';
    function findTextNodes(node, text) {
        let result = [];
        fatherList += text; // 记录节点路径
    
        // 检查当前节点,并做wikilink的识别和路径排除,这里做排除名单应该很方便
        if (node.type === 'text' && node.content.startsWith('[[') && !fatherList.endsWith('precode'))   // [!code highlight]{
            node.content = node.content.replace('[[', '<a>test').replace(']]', '</a>');
            result.push(node);
        }
    
        // 如果有子节点,递归遍历
        if (node.children) {
            node.children.forEach((child) => {
                result = result.concat(findTextNodes(child, node.name));
            });
        }
    
        return result;
    }
    
    const result = [];
    
    ast.forEach((node) => {
        const temp = findTextNodes(node, node.name);
        let fatherList = '';
    });
    
    const html = HTML.stringify(ast);

    这是我目前能找到的最优方案了,不过这个方案应该也是偏向于解析成dom,不过这个库没有进行更多消耗资源的操作,应该是比传统的解析库性能更优秀的。

    各种标签测试

    WikiLink测试

    [[note1]]

    [[note2]]
    

    [[note3]]

    [[note4]]
    • [[note5]]

    [[note6]]

    [[note7]]

    [[note8]]

    @1900’Blog
    All work and no play makes Jack a dull boy
    @1900'Blog

    https://cms.1900.live/ni-hao-astro/

    测试一下文字包裹[[note9]]后的解析情况

    [[note9]]

    实际引用测试

    已经初步完成联动,目前的考虑的策略顺序如下,

    博客中

    1. 优先从CMS中去匹配文章 Title 一致的文章
    2. 如果博客内没有一直的则去Obsidian中匹配文件Title字段一致的文章
    3. 如果有多条数据只返回最前面一条
    4. 如果都没有则不进行转换

    Obsidian中

    1. 优先匹配Obsidian中Title一致的文章
    2. 如果没有则去博客内匹配Title一致的文章
    3. 如果有多条数据只返回最前面一条
    4. 如果都没有则不进行转换

    如Obsidian中的 About 这篇文章,在博客中以 [[About]] 写入,最终文章内就会解析成 [[About]] 。

    因为Astro的本地路由都是基于网站根目录,所以我们只要将博客和Obsidian的文件分别进行静态生成,并在生成过程干涉 [[]] 的转换成 <a href=''><a/> 标签,然后给出正确的slug,便可以很方便的实现SPA形式的页面跳转。

    完。



沪ICP备19023445号-2号
友情链接