首页 > 开发 > JavaScript > 正文

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

2020-03-22 20:25:49
字体:
来源:转载
供稿:网友
学了几天正则,差不多该总结整理写成果了,之前就想写语法高亮匹配来着,不过水平不够,看着例子都不理解。那么我们来分析下两位大神 次碳酸钴 和 Barret Lee 语法高亮实现。先说 Barret Lee 的这篇 《几个小例子教你如何实现html' target='_blank'>正则表达式highlight高亮》之前看的时候只觉的神奇,特别是下面那个一步一步分开匹配的例子,更是霸气测漏,不过作者也说了,分开只是为了演示方便,可以很直观的看到这一步匹配了什么,不然一步到位匹配完成,你都不知道发生了什么就处理完毕了。
来看下他的正则如何实现正则表达式的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 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 i++) {
if (s = a[i]) {
s = htmlEncode(s);
switch (i) {
case 1: //注释 com
return ' span ' + s + ' /span
case 2: //字符串 str
return ' span ' + s + ' /span
case 3: //true|false|null|undefined|NaN val
return ' span ' + s + ' /span
case 4: //关键词 kwd
return ' span ' + s + ' /span
case 5: //内置对象 obj
return ' span ' + s + ' /span
case 6: //数字 num
return ' span ' + s + ' /span
case 7: //正则 reg
return htmlEncode(a[0]).replace(s, ' span ' + s + ' /span
}
}
}
return htmlEncode(a[0]);
});
code = code.replace(/(?:/s*/*/s*|(?: )*/*(?: )*)(@/w+)/b/g, ' * span $1 /span ') // 匹配注释中的标记
.replace(/(/w+)(/s*/(|(?: )*/()|(/w+)(/s*=/s*function|(?: )*=(?: )*function)/g, ' span $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 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 i++) {
if (s = a[i]) {
s = htmlEncode(s);
switch (i) {
case 1: //注释 com
return ' span ' + s + ' /span
case 2: //字符串 str
return ' span ' + s + ' /span
case 3: //true|false|null|undefined|NaN val
return ' span ' + s + ' /span
case 4: //关键词 kwd
return ' span ' + s + ' /span
case 5: //内置对象 obj
return ' span ' + s + ' /span
case 6: //数字 num
return ' span ' + s + ' /span
case 7: //正则 reg
return htmlEncode(a[0]).replace(s, ' span ' + s + ' /span
}
}
}
return htmlEncode(a[0]);
});
code = code.replace(/(?:/s*/*/s*|(?:)*/*(?:)*)(@/w+)/b/g, '* span $1 /span ') // 匹配注释中的标记
.replace(/(/w+)(/s*/(|(?:)*/()|(/w+)(/s*=/s*function|(?:)*=(?:)*function)/g, ' span $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 i++) {
if (s = a[i]) {
s = htmlEncode(s);
switch (i) {
case 1: //注释 com
return ' span ' + s + ' /span
case 2: //字符串 str
return ' span ' + s + ' /span
case 3: //true|false|null|undefined|NaN val
return ' span ' + s + ' /span
case 4: //关键词 kwd
return ' span ' + s + ' /span
case 5: //内置对象 obj
return ' span ' + s + ' /span
case 6: //数字 num
return ' span ' + s + ' /span
case 7: //正则 reg
return htmlEncode(a[0]).replace(s, ' span ' + s + ' /span
}
}
}
return htmlEncode(a[0]);
});
code = code.replace(/(?:/s*/*/s*|(?:)*/*(?:)*)(@/w+)/b/g, '* span $1 /span ') // 匹配注释中的标记
.replace(/(/w+)(/s*/(|(?:)*/()|(/w+)(/s*=/s*function|(?:)*=(?:)*function)/g, ' span $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累了。。PHP教程

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表