欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

js 玩转正则表达式之语法高亮

程序员文章站 2023-02-23 16:36:46
学了几天正则,差不多该总结整理写成果了,之前就想写语法高亮匹配来着,不过水平不够,看着例子都不理解。 那么我们来分析下两位大神 次碳酸钴 和 barret lee 语法高...

学了几天正则,差不多该总结整理写成果了,之前就想写语法高亮匹配来着,不过水平不够,看着例子都不理解。

那么我们来分析下两位大神 次碳酸钴 和 barret lee 语法高亮实现。

先说 barret lee 的这篇 《》

之前看的时候只觉的神奇,特别是下面那个一步一步分开匹配的例子,更是霸气测漏,不过作者也说了,分开只是为了演示方便,可以很直观的看到这一步匹配了什么,不然一步到位匹配完成,你都不知道发生了什么就处理完毕了。
来看下他的正则

复制代码 代码如下:

(/^\s+|\s+$/) // 匹配首尾空格
(/(["'])(?:\\.|[^\\\n])*?\1/) // 匹配字符串
(/\/(?!\*|span).+\/(?!span)[gim]*/) // 匹配正则 span 是他上次处理加上的,我觉得这里不应该出现
(/(\/\/.*|\/\*[\s\s]+?\*\/)/) // 匹配注释
(/(\*\s*)(@\w+)(?=\s*)/) // 匹配 注释中的标记
(/\b(break|continue|do|for|in|function|if|else|return|switch|throw|try|catch|finally|var|while|with|case|new|typeof|instance|delete|void|object|array|string|number|boolean|function|regexp|date|math|window|document|navigator|location|true|false|null|undefined|nan)\b/) // 匹配关键词

小胡子哥可能是不想重复造*,只是想弄清楚如何造这样的*而已,所以他写这个东西点到即止,没有深入详细的处理,做的比较粗糙。
当然我也不是说他什么,只是简单评论一下而已,毕竟优秀的语法高亮插件多的是,没必要自己重复造,学习下原理即可。

我们再来分析下 次碳酸钴 这篇 《如何实现正则表达式的javascript的代码高亮
其实这篇已经分析的非常详细了,我只能简单补充说明下。
次碳酸钴 思维一向比较严谨,这篇文章之前我看了一个多小时,只能看个大概,这次重新分析了一遍,然后自己实现了一遍,竟然也花去我半天时间,
不过非常值得,真心学到了很多。

先来看一下大体的逻辑吧。

复制代码 代码如下:

(\/\/.*|\/\*[\s\s]+?\*\/) // 匹配注释
((["'])(?:\\.|[^\\\n])*?\3) // 匹配字符串
\b(break|continue|do|for|in|function|if|else|return|switch|this|throw|try|catch|finally|var|while|with|case|new|typeof|instance|delete|void)\b // 匹配关键词
\b(object|array|string|number|boolean|function|regexp|date|math|window|document|navigator|location)\b // 匹配内置对象
\b(true|false)\b // 匹配布尔值
\b(null|undefined|nan)\b // 匹配各种空值, 我觉得这个和布尔值一组比较合适。
(?:[^\w\d]|\$)[\$\w]* // 匹配普通的变量名
(0[xx][0-9a-fa-f]+|\d+(?:\.\d+)?(?:[ee]\d+)?) // 匹配数字 (前者不占用,这里就会有问题)
(?:[^\)\]\}]|^)(\/(?!\*)(?:\\.|[^\\\/\n])+?\/[gim]*) // 匹配正则
[\s\s] // 其他不能匹配的任意值

原文对最后一个 [\s\s] 的描述:我们必须匹配到每一个字符。因为它们都需要做一次html转义。
然后下面有详细的代码。

这是一篇非常不错的文章,我前前后后至少看了不下10次了,前两天才差不多完全明白。

不过这个代码还有一些小小的瑕疵,比如字符串不能匹配折行那种,字符串匹配优化。

还有数字匹配不够全面只能匹配 0xff, 12.34, 1e3 这几类,如 .123 12.3e+3 等格式都无法匹配到。
还有关键词顺序我觉得可以稍微优化下。
因为 传统型nfa 引擎的只是从左往右匹配,匹配到了就停止下一个分支的操作。
所以把最常出现的关键词放前面,可以提升一部分性能。
最后,最好是 new regexp 这样对于代码量大的代码性能上会有所提升。

下面就给出我的正则和简单的demo吧。(其实只是对 次碳酸钴 源码的优化而已。。)
先来看正则部分:

复制代码 代码如下:

(\/\/.*|\/\*[\s\s]*?\*\/) // 匹配注释 没改
("(?:[^"\\]|\\[\s\s])*"|'(?:[^'\\]|\\[\s\s])*') // 匹配注释 优化过
\b(true|false|null|undefined|nan)\b // 匹配 布尔和空值,这几个比较常用,分组提前
\b(var|for|if|else|return|this|while|new|function|switch|case|typeof|do|in|throw|try|catch|finally|with|instance|delete|void|break|continue)\b // 匹配关键词,关键词顺序改了下
\b(document|date|math|window|object|location|navigator|array|string|number|boolean|function|regexp)\b //内置对象,单词顺序改了下
(?:[^\w\d]|\$)[\$\w]* // 匹配普通的变量名 没改
(0[xx][0-9a-fa-f]+|\d+(?:\.\d+)?(?:[ee][+-]?\d+)?|\.\d+(?:[ee][+-]?\d+)?) // 匹配数字,修复了匹配
(?:^|[^\)\]\}])(\/(?!\*)(?:\\.|[^\\\/\n])+?\/[gim]*) // 匹配正则,这个最复杂,情况很多,我暂时没实力修改
[\s\s] // 匹配其他

合并了布尔和空值一个分组,然后优化了正则分组,所以比他减少了2个分组。
他 2,3 是字符串分组,因为 (["']) 捕获了前面的引号,而我的正则没这么做。
这个 (true|false|null|undefined|nan) 如果你不喜欢放在一个分组了,分开也行、
是不是同一个分组,只是为了区分着色而已。
sublime text 下 true|false|null|undefined|nan 都是一个颜色,而 notepad++ 则只着色了 true|false ,我只想说 呵呵。

好了,差不多该给例子了。
我相信,不少人在看到这之前已经关掉了,或者只是拉了下滚动条然后关掉了。
不过我写这个就是为了给这些认真看下来的朋友,只要有一个人看,我觉得就不会白写了。
例子:

复制代码 代码如下:

// 单行注释
/**
 * 多行注释
 * @date 2014-05-12 22:24:37
 * @name 测试一下
 */
var str1 = "123\"456";
var str2 = '123\'456';
var str3 = "123\
456";

var num = 123;
var arr = [12, 12.34, .12, 1e3, 1e+3, 1e-3, 12.34e3, 12.34e+3, 12.34e-3, .1234e3];
var arr = ["12", "12.34", '.12, 1e3', '1e+3, 1e-3', '12.34e3, 12.34e+3, 12.34e-3', ".1234e3"];
var arr = [/12", "12.34/, /"12\/34"/];

for (var i=0; i<1e3; i++) {
  var node = document.getelementbyid("a"+i);
  arr.push(node);
}

function test () {
  return true;
}
test();

 

(function(window, undefined) {
    var _re_js = new regexp('(\\/\\/.*|\\/\\*[\\s\\s]*?\\*\\/)|("(?:[^"\\\\]|\\\\[\\s\\s])*"|\'(?:[^\'\\\\]|\\\\[\\s\\s])*\')|\\b(true|false|null|undefined|nan)\\b|\\b(var|for|if|else|return|this|while|new|function|switch|case|typeof|do|in|throw|try|catch|finally|with|instance|delete|void|break|continue)\\b|\\b(document|date|math|window|object|location|navigator|array|string|number|boolean|function|regexp)\\b|(?:[^\\w\\d]|\\$)[\\$\\w]*|(0[xx][0-9a-fa-f]+|\\d+(?:\\.\\d+)?(?:[ee][+-]?\\d+)?|\\.\\d+(?:[ee][+-]?\\d+)?)|(?:^|[^\\)\\]\\}])(\\/(?!\\*)(?:\\\\.|[^\\\\\\/\\n])+?\\/[gim]*)|[\\s\\s]', 'g');

    function prettify(node) {
        var code = node.innerhtml.replace(/\r\n|[\r\n]/g, "\n").replace(/^\s+|\s+$/g, "");
        code = code.replace(_re_js, function() {
            var s, a = arguments;
            for (var i = 1; i <= 7; i++) {
                if (s = a[i]) {
                    s = htmlencode(s);
                    switch (i) {
                        case 1: //注释 com
                            return '<span class="com">' + s + '</span>';
                        case 2: //字符串 str
                            return '<span class="str">' + s + '</span>';
                        case 3: //true|false|null|undefined|nan val
                            return '<span class="val">' + s + '</span>';
                        case 4: //关键词 kwd
                            return '<span class="kwd">' + s + '</span>';
                        case 5: //内置对象 obj
                            return '<span class="obj">' + s + '</span>';
                        case 6: //数字 num
                            return '<span class="num">' + s + '</span>';
                        case 7: //正则 reg
                            return htmlencode(a[0]).replace(s, '<span class="reg">' + s + '</span>');
                    }
                }
            }
            return htmlencode(a[0]);
        });
        code = code.replace(/(?:\s*\*\s*|(?: )*\*(?: )*)(@\w+)\b/g, ' * <span class="comkey">$1</span>') // 匹配注释中的标记
                   .replace(/(\w+)(\s*\(|(?: )*\()|(\w+)(\s*=\s*function|(?: )*=(?: )*function)/g, '<span class="func">$1</span>$2') // 匹配函数
        return code;
    }


    function htmlencode(str) {
        var i, s = {
                //"&": /&/g,
                """: /"/g,
                "'": /'/g,
                "<": //g,
                "<br>": /\n/g,
                " ": / /g,
                "  ": /\t/g
            };
        for (i in s) {
            str = str.replace(s[i], i);
        }
        return str;
    }

    window.prettify = prettify;
})(window);

你们可以用下面的代码进行测试。

代码:

复制代码 代码如下:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>test</title>
    <style>
    /* 高亮样式 */
    *{font-size:12px;}
    code{word-break:break-all;}

    .com {color:#008000;} /* 注释 */
    .comkey {color:#ffa500;} /* 注释标记 */
    .str {color:#808080;} /* 字符串 */
    .val {color:#000080;} /* true|false|null|undefined|nan */
    .kwd {color:#000080;font:bold 12px 'comic sans ms', sans-serif;} /* 关键词 */
    .obj {color:#000080;} /* 内置对象 */
    .num {color:#ff0000;} /* 数字 */
    .reg {color:#8000ff;} /* 正则 */
    .func {color:#a355b9;} /* 函数 */
    </style>
</head>
<body>

<code id="regdemon">
// 单行注释
/**
 * 多行注释
 * @date 2014-05-12 22:24:37
 * @name 测试一下
 */
var str1 = "123\"456";
var str2 = '123\'456';
var str3 = "123\
456";

var num = 123;
var arr = [12, 12.34, .12, 1e3, 1e+3, 1e-3, 12.34e3, 12.34e+3, 12.34e-3, .1234e3];
var arr = ["12", "12.34", '.12, 1e3', '1e+3, 1e-3', '12.34e3, 12.34e+3, 12.34e-3', ".1234e3"];
var arr = [/12", "12.34/, /"12\/34"/];

for (var i=0; i<1e3; i++) {
    var node = document.getelementbyid("a"+i);
    arr.push(node);
}

function test () {
    return true;
}
test();

 

(function(window, undefined) {
    var _re_js = new regexp('(\\/\\/.*|\\/\\*[\\s\\s]*?\\*\\/)|("(?:[^"\\\\]|\\\\[\\s\\s])*"|\'(?:[^\'\\\\]|\\\\[\\s\\s])*\')|\\b(true|false|null|undefined|nan)\\b|\\b(var|for|if|else|return|this|while|new|function|switch|case|typeof|do|in|throw|try|catch|finally|with|instance|delete|void|break|continue)\\b|\\b(document|date|math|window|object|location|navigator|array|string|number|boolean|function|regexp)\\b|(?:[^\\w\\d]|\\$)[\\$\\w]*|(0[xx][0-9a-fa-f]+|\\d+(?:\\.\\d+)?(?:[ee][+-]?\\d+)?|\\.\\d+(?:[ee][+-]?\\d+)?)|(?:^|[^\\)\\]\\}])(\\/(?!\\*)(?:\\\\.|[^\\\\\\/\\n])+?\\/[gim]*)|[\\s\\s]', 'g');

    function prettify(node) {
        var code = node.innerhtml.replace(/\r\n|[\r\n]/g, "\n").replace(/^\s+|\s+$/g, "");
        code = code.replace(_re_js, function() {
            var s, a = arguments;
            for (var i = 1; i <= 7; i++) {
                if (s = a[i]) {
                    s = htmlencode(s);
                    switch (i) {
                        case 1: //注释 com
                            return '<span class="com">' + s + '</span>';
                        case 2: //字符串 str
                            return '<span class="str">' + s + '</span>';
                        case 3: //true|false|null|undefined|nan val
                            return '<span class="val">' + s + '</span>';
                        case 4: //关键词 kwd
                            return '<span class="kwd">' + s + '</span>';
                        case 5: //内置对象 obj
                            return '<span class="obj">' + s + '</span>';
                        case 6: //数字 num
                            return '<span class="num">' + s + '</span>';
                        case 7: //正则 reg
                            return htmlencode(a[0]).replace(s, '<span class="reg">' + s + '</span>');
                    }
                }
            }
            return htmlencode(a[0]);
        });
        code = code.replace(/(?:\s*\*\s*|(?: )*\*(?: )*)(@\w+)\b/g, ' * <span class="comkey">$1</span>') // 匹配注释中的标记
                   .replace(/(\w+)(\s*\(|(?: )*\()|(\w+)(\s*=\s*function|(?: )*=(?: )*function)/g, '<span class="func">$1</span>$2') // 匹配函数
        return code;
    }


    function htmlencode(str) {
        var i, s = {
                //"&": /&/g,
                """: /"/g,
                "'": /'/g,
                "<": /</g,
                ">": />/g,
                "<br>": /\n/g,
                " ": / /g,
                "  ": /\t/g
            };
        for (i in s) {
            str = str.replace(s[i], i);
        }
        return str;
    }

    window.prettify = prettify;
})(window);
</code>

<script>
(function(window, undefined) {
    var _re_js = new regexp('(\\/\\/.*|\\/\\*[\\s\\s]*?\\*\\/)|("(?:[^"\\\\]|\\\\[\\s\\s])*"|\'(?:[^\'\\\\]|\\\\[\\s\\s])*\')|\\b(true|false|null|undefined|nan)\\b|\\b(var|for|if|else|return|this|while|new|function|switch|case|typeof|do|in|throw|try|catch|finally|with|instance|delete|void|break|continue)\\b|\\b(document|date|math|window|object|location|navigator|array|string|number|boolean|function|regexp)\\b|(?:[^\\w\\d]|\\$)[\\$\\w]*|(0[xx][0-9a-fa-f]+|\\d+(?:\\.\\d+)?(?:[ee][+-]?\\d+)?|\\.\\d+(?:[ee][+-]?\\d+)?)|(?:^|[^\\)\\]\\}])(\\/(?!\\*)(?:\\\\.|[^\\\\\\/\\n])+?\\/[gim]*)|[\\s\\s]', 'g');

    function prettify(node) {
        var code = node.innerhtml.replace(/\r\n|[\r\n]/g, "\n").replace(/^\s+|\s+$/g, "");
        code = code.replace(_re_js, function() {
            var s, a = arguments;
            for (var i = 1; i <= 7; i++) {
                if (s = a[i]) {
                    s = htmlencode(s);
                    switch (i) {
                        case 1: //注释 com
                            return '<span class="com">' + s + '</span>';
                        case 2: //字符串 str
                            return '<span class="str">' + s + '</span>';
                        case 3: //true|false|null|undefined|nan val
                            return '<span class="val">' + s + '</span>';
                        case 4: //关键词 kwd
                            return '<span class="kwd">' + s + '</span>';
                        case 5: //内置对象 obj
                            return '<span class="obj">' + s + '</span>';
                        case 6: //数字 num
                            return '<span class="num">' + s + '</span>';
                        case 7: //正则 reg
                            return htmlencode(a[0]).replace(s, '<span class="reg">' + s + '</span>');
                    }
                }
            }
            return htmlencode(a[0]);
        });
        code = code.replace(/(?:\s*\*\s*|(?: )*\*(?: )*)(@\w+)\b/g, ' * <span class="comkey">$1</span>') // 匹配注释中的标记
                   .replace(/(\w+)(\s*\(|(?: )*\()|(\w+)(\s*=\s*function|(?: )*=(?: )*function)/g, '<span class="func">$1</span>$2') // 匹配函数
        return code;
    }


    function htmlencode(str) {
        var i, s = {
                //"&": /&/g,
                """: /"/g,
                "'": /'/g,
                "<": /</g,
                ">": />/g,
                "<br>": /\n/g,
                " ": / /g,
                "  ": /\t/g
            };
        for (i in s) {
            str = str.replace(s[i], i);
        }
        return str;
    }

    window.prettify = prettify;
})(window);

var code = document.getelementbyid("regdemon");
code.innerhtml = prettify(code);
</script>
</body>
</html>

差不多结合了 小胡子哥 和 次碳酸钴 两个思路的结果,现在比较完善了。
兼容性什么的还没测试,也没必要测试了,我也没打算自己写各种语法的高亮,太tm累了。。