學了幾天正則,差很少該總結整理寫成果了,以前就想寫語法高亮匹配來着,不過水平不夠,看着例子都不理解。
今天就分析下 次碳酸鈷 和 Barret Lee 語法高亮實現。javascript
先說 Barret Lee 的這篇 《玩轉正則之highlight高亮》
以前看的時候只覺的神奇,特別是下面那個一步一步分開匹配的例子,更是霸氣測漏,不過做者也說了,分開只是爲了演示方便,能夠很直觀的看到這一步匹配了什麼,否則一步到位匹配完成,你都不知道發生了什麼就處理完畢了。
來看下他的正則html
(/^\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/) // 匹配關鍵詞java
小鬍子哥多是不想重複造輪子,只是想弄清楚如何造這樣的輪子而已,因此他寫這個東西點到即止,沒有深刻詳細的處理,作的比較粗糙。
固然我也不是說他什麼,只是簡單評論一下而已,畢竟優秀的語法高亮插件多的是,不必本身重複造,學習下原理便可。node
咱們再來分析下 次碳酸鈷 這篇 《使用正則表達式實現JavaScript的代碼高亮》
其實這篇已經分析的很是詳細了,我只能簡單補充說明下。
次碳酸鈷 思惟一貫比較嚴謹,這篇文章以前我看了一個多小時,只能看個大概,此次從新分析了一遍,而後本身實現了一遍,居然也花去我半天時間,
不過很是值得,真心學到了不少。web
先來看一下大致的邏輯吧。
(\/\/.*|\/\*[\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次了,前兩天才差很少徹底明白。
不過這個代碼還有一些小小的瑕疵,好比字符串不能匹配折行那種,字符串匹配優化,我以前文章大篇幅的討論了這個問題。
詳見:《js 正則學習小記之匹配字符串》 和 《js 正則學習小記之匹配字符串優化篇》
還有數字匹配不夠全面只能匹配 0xff, 12.34, 1e3 這幾類,如 .123 12.3e+3 等格式都沒法匹配到。
還有關鍵詞順序我以爲能夠稍微優化下。
由於 傳統型NFA 引擎的只是從左往右匹配,匹配到了就中止下一個分支的操做。
因此把最常出現的關鍵詞放前面,能夠提高一部分性能。
最後,最好是 new RegExp 這樣對於代碼量大的代碼性能上會有所提高。函數
下面就給出個人正則和簡單的demo吧。(其實只是對 次碳酸鈷 源碼的優化而已。。)
先來看正則部分:post
(\/\/.*|\/\*[\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);
這就是渲染後的代碼了,因爲代碼比較長,複製上來後被博客園格式化後代碼格式不對了,因此我直接把渲染後的html直接複製上來了。
大家能夠用下面的代碼進行測試。
代碼:
<!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累了。。
好了,今天花了比較多的時間寫例子,也沒時間檢查了,若是有不對的地方請跟帖謝謝。