原文:js 正则练习之语法高亮

学了几天正则,差不多该总结整理写成果了,之前就想写语法高亮匹配来着,不过水平不够,看着例子都不理解。
今天就分析下 次碳酸钴Barret Lee 语法高亮实现。

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

(/^\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次了,前两天才差不多完全明白。
不过这个代码还有一些小小的瑕疵,比如字符串不能匹配折行那种,字符串匹配优化,我之前文章大篇幅的讨论了这个问题。
详见:《js 正则学习小记之匹配字符串》 和 《js 正则学习小记之匹配字符串优化篇
还有数字匹配不够全面只能匹配 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);

这就是渲染后的代码了,由于代码比较长,复制上来后被博客园格式化后代码格式不对了,所以我直接把渲染后的html直接复制上来了。

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

代码:

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>test</title>
  6. <style>
  7. /* 高亮样式 */
  8. *{font-size:12px;}
  9. code{word-break:break-all;}
  10.  
  11. .com {color:#008000;} /* 注释 */
  12. .comkey {color:#FFA500;} /* 注释标记 */
  13. .str {color:#808080;} /* 字符串 */
  14. .val {color:#000080;} /* true|false|null|undefined|NaN */
  15. .kwd {color:#000080;font:bold 12px 'comic sans ms', sans-serif;} /* 关键词 */
  16. .obj {color:#000080;} /* 内置对象 */
  17. .num {color:#FF0000;} /* 数字 */
  18. .reg {color:#8000FF;} /* 正则 */
  19. .func {color:#A355B9;} /* 函数 */
  20. </style>
  21. </head>
  22. <body>
  23.  
  24. <code id="regdemon">
  25. // 单行注释
  26. /**
  27. * 多行注释
  28. * @date 2014-05-12 22:24:37
  29. * @name 测试一下
  30. */
  31. var str1 = "123\"456";
  32. var str2 = '123\'456';
  33. var str3 = "123\
  34. 456";
  35.  
  36. var num = 123;
  37. var arr = [12, 12.34, .12, 1e3, 1e+3, 1e-3, 12.34e3, 12.34e+3, 12.34e-3, .1234e3];
  38. var arr = ["12", "12.34", '.12, 1e3', '1e+3, 1e-3', '12.34e3, 12.34e+3, 12.34e-3', ".1234e3"];
  39. var arr = [/12", "12.34/, /"12\/34"/];
  40.  
  41. for (var i=0; i<1e3; i++) {
  42. var node = document.getElementById("a"+i);
  43. arr.push(node);
  44. }
  45.  
  46. function test () {
  47. return true;
  48. }
  49. test();
  50.  
  51. (function(window, undefined) {
  52. 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');
  53.  
  54. function prettify(node) {
  55. var code = node.innerHTML.replace(/\r\n|[\r\n]/g, "\n").replace(/^\s+|\s+$/g, "");
  56. code = code.replace(_re_js, function() {
  57. var s, a = arguments;
  58. for (var i = 1; i <= 7; i++) {
  59. if (s = a[i]) {
  60. s = htmlEncode(s);
  61. switch (i) {
  62. case 1: //注释 com
  63. return '<span class="com">' + s + '</span>';
  64. case 2: //字符串 str
  65. return '<span class="str">' + s + '</span>';
  66. case 3: //true|false|null|undefined|NaN val
  67. return '<span class="val">' + s + '</span>';
  68. case 4: //关键词 kwd
  69. return '<span class="kwd">' + s + '</span>';
  70. case 5: //内置对象 obj
  71. return '<span class="obj">' + s + '</span>';
  72. case 6: //数字 num
  73. return '<span class="num">' + s + '</span>';
  74. case 7: //正则 reg
  75. return htmlEncode(a[0]).replace(s, '<span class="reg">' + s + '</span>');
  76. }
  77. }
  78. }
  79. return htmlEncode(a[0]);
  80. });
  81. code = code.replace(/(?:\s*\*\s*|(?:&nbsp;)*\*(?:&nbsp;)*)(@\w+)\b/g, '&nbsp;*&nbsp;<span class="comkey">$1</span>') // 匹配注释中的标记
  82. .replace(/(\w+)(\s*\(|(?:&nbsp;)*\()|(\w+)(\s*=\s*function|(?:&nbsp;)*=(?:&nbsp;)*function)/g, '<span class="func">$1</span>$2') // 匹配函数
  83. return code;
  84. }
  85.  
  86. function htmlEncode(str) {
  87. var i, s = {
  88. //"&amp;": /&/g,
  89. "&quot;": /"/g,
  90. "'": /'/g,
  91. "&lt;": /</g,
  92. "&gt;": />/g,
  93. "<br>": /\n/g,
  94. "&nbsp;": / /g,
  95. "&nbsp;&nbsp;": /\t/g
  96. };
  97. for (i in s) {
  98. str = str.replace(s[i], i);
  99. }
  100. return str;
  101. }
  102.  
  103. window.prettify = prettify;
  104. })(window);
  105. </code>
  106.  
  107. <script>
  108. (function(window, undefined) {
  109. 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');
  110.  
  111. function prettify(node) {
  112. var code = node.innerHTML.replace(/\r\n|[\r\n]/g, "\n").replace(/^\s+|\s+$/g, "");
  113. code = code.replace(_re_js, function() {
  114. var s, a = arguments;
  115. for (var i = 1; i <= 7; i++) {
  116. if (s = a[i]) {
  117. s = htmlEncode(s);
  118. switch (i) {
  119. case 1: //注释 com
  120. return '<span class="com">' + s + '</span>';
  121. case 2: //字符串 str
  122. return '<span class="str">' + s + '</span>';
  123. case 3: //true|false|null|undefined|NaN val
  124. return '<span class="val">' + s + '</span>';
  125. case 4: //关键词 kwd
  126. return '<span class="kwd">' + s + '</span>';
  127. case 5: //内置对象 obj
  128. return '<span class="obj">' + s + '</span>';
  129. case 6: //数字 num
  130. return '<span class="num">' + s + '</span>';
  131. case 7: //正则 reg
  132. return htmlEncode(a[0]).replace(s, '<span class="reg">' + s + '</span>');
  133. }
  134. }
  135. }
  136. return htmlEncode(a[0]);
  137. });
  138. code = code.replace(/(?:\s*\*\s*|(?:&nbsp;)*\*(?:&nbsp;)*)(@\w+)\b/g, '&nbsp;*&nbsp;<span class="comkey">$1</span>') // 匹配注释中的标记
  139. .replace(/(\w+)(\s*\(|(?:&nbsp;)*\()|(\w+)(\s*=\s*function|(?:&nbsp;)*=(?:&nbsp;)*function)/g, '<span class="func">$1</span>$2') // 匹配函数
  140. return code;
  141. }
  142.  
  143. function htmlEncode(str) {
  144. var i, s = {
  145. //"&amp;": /&/g,
  146. "&quot;": /"/g,
  147. "'": /'/g,
  148. "&lt;": /</g,
  149. "&gt;": />/g,
  150. "<br>": /\n/g,
  151. "&nbsp;": / /g,
  152. "&nbsp;&nbsp;": /\t/g
  153. };
  154. for (i in s) {
  155. str = str.replace(s[i], i);
  156. }
  157. return str;
  158. }
  159.  
  160. window.prettify = prettify;
  161. })(window);
  162.  
  163. var code = document.getElementById("regdemon");
  164. code.innerHTML = prettify(code);
  165. </script>
  166. </body>
  167. </html>

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

好了,今天花了比较多的时间写例子,也没时间检查了,如果有不对的地方请跟帖谢谢。

js 正则练习之语法高亮的更多相关文章

  1. JS正则四个反斜杠的含义

    我们首先来看如下代码,在浏览器中输出的是什么? // 在浏览器中输出的 console.log('\\'); // 输出 \ console.log('\\\\'); // 输出 \\ 一:js正则直 ...

  2. 用 highlight.js 为文章中的代码添加语法高亮

    来源:http://www.ghostchina.com/adding-syntax-highlighting-to-ghost-using-highlight-js/ --------------- ...

  3. js 语法高亮插件之 Prism.js

    之前也介绍过几款语法高亮插件<为博客园选择一个小巧霸气的语法高亮插件>以及关于他们的综合性能<再议 语法高亮插件的选择>.今天在小影志博客看到<使用 Prism.js 实 ...

  4. JavaScript语法高亮库highlight.js使用

    highlight.js是一款基于JavaScript的语法高亮库,目前支持125种编程语言,有63种可供选择的样式,而且能够做到语言自动识别,和目前主流的JS框架都能兼容,可以混合使用. 这款高亮库 ...

  5. js 正则学习小记之匹配字符串

    原文:js 正则学习小记之匹配字符串 今天看了第5章几个例子,有点收获,记录下来当作回顾也当作分享. 关于匹配字符串问题,有很多种类型,今天讨论 js 代码里的字符串匹配.(因为我想学完之后写个语法高 ...

  6. 浅谈 js 正则之 test 方法

    原文:浅谈 js 正则之 test 方法 其实我很少用这个,所以之前一直没注意这个问题,自从落叶那厮写了个变态的测试我才去看了下这东西.先来看个东西吧. var re = /\d/; console. ...

  7. ace -- 语法高亮

    Creating a Syntax Highlighter for Ace 给ace创建一个语法高亮 Creating a new syntax highlighter for Ace is extr ...

  8. js 正则学习小记之匹配字符串字面量

    今天看了第5章几个例子,有点收获,记录下来当作回顾也当作分享. 关于匹配字符串问题,有很多种类型,今天讨论 js 代码里的字符串匹配.(因为我想学完之后写个语法高亮练手,所以用js代码当作例子) va ...

  9. 代码语法高亮踩坑-原理,问题, PRE元素及htmlentity

    语法高亮库基础原理 在研究使用能够在web页面上代码语法高显的解决方案时,发现有很多现成的开源库.比较中意的有prism.js,highlightjs.他们的原理基本上核心就两点: 1. 利用html ...

随机推荐

  1. [LeetCode136]Single Number寻找一个数组里只出现一次的数

    题目: Given an array of integers, every element appears twice except for one. Find that single one. No ...

  2. Android - 和其他APP交互 - 把用户带到其他app

    Android的重要功能之一就是app可以根据要执行的操作让用户启动另外一个app.例如,app有一个商业地址然后想要在地图上显示,并不需要在app中加一个显示地图的activity,可以直接用Int ...

  3. charles抓包

    charles使用教程指南 charles使用教程指南 前言 移动APP抓包 PC端抓包 查看模式 其他功能 问题汇总 1. 前言: Charles是一款抓包修改工具,相比起burp,charles具 ...

  4. SQLSERVER手动增长日志文件和数据文件

    原文:SQLSERVER手动增长日志文件和数据文件 SQLSERVER手动增长日志文件和数据文件 手动增长日志文件,实际上就是修改日志文件的大小  size 的单位是MB 下面设置日志文件大小是204 ...

  5. REQIMPORT-购买内部应用程序(R12.2.3)

     採购内部申请(R12.2.3) --US Program:Requisition Import Short Name:REQIMPORT Application:Purchasing Execu ...

  6. ObjectStreamDemo

    当你需要存储相同类型的数据时,使用固定长度的记录格式是一个不错的选择.但,在OOP中创建的对象很少全部都具有相同的类型. 例如,你可能有一个称为staff(见下面demo)的array,它名义上是一个 ...

  7. BZOJ 2588 Count on a tree (COT) 是持久的段树

    标题效果:两棵树之间的首次查询k大点的权利. 思维:树木覆盖树,事实上,它是正常的树木覆盖了持久段树. 由于使用权值段树可以寻求区间k大,然后应用到持久段树思想,间隔可以做减法.详见代码. CODE: ...

  8. BC 2015在百度之星程序设计大赛 - 预赛(1)(矩形区域-旋转卡)

    矩形区域 Accepts: 717 Submissions: 1619 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 ...

  9. centos6.5 64位 openvpn安装配置(转)

    查看系统版本cat /etc/redhat-releaseCentOS release 6.5 (Final) 查看内核和cpu架构uname -rm2.6.32-431.el6.x86_64 x86 ...

  10. Java TCP/UDP网络通信编程

    本文转自:http://www.cnblogs.com/cdtarena/archive/2013/04/10/3012282.html 网络应用中基本上都是TCP(Transmission Cont ...