dp.SyntaxHighlighter代码分析


文章截图 - 更好的排版
源代码下载

dp.SyntaxHighlighter代码分析


dp.SyntaxHighlighter作为一个最常用的JavaScript代码高亮工具受到广泛的欢迎。
那么你有没有想知道它的内部实现机制是什么,本文在对其分析后,抽取核心代码以重现其功能。
注:本文中的代码高亮使用的就是手工打造的dp.SyntaxHighlighter的简化版。

核心思想
1. 首先定义了一系列的正则表达式用来匹配所有的高亮代码,包括关键字、单行注释、多行注释、字符串, 如果你仔细观察高亮后的JavaScript代码,你会发现也只有这些代码被高亮显示了,是不是很简单。
var keywords = 'abstract boolean break byte case catch char class const continue debugger ' +
 'default delete do double else enum export extends false final finally float ' +
 'for function goto if implements import in instanceof int interface long native ' +
 'new null package private protected public return short static super switch ' +
 'synchronized this throw throws transient true try typeof var void volatile while with';
var regexList = [{ regex: new RegExp('/\\*[\\s\\S]*?\\*/', 'gm'), css: 'comment' },
{ regex: new RegExp('//.*$', 'gm'), css: 'comment' },
{ regex: new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"', 'g'), css: 'string' },
{ regex: new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'", 'g'), css: 'string' },
{ regex: new RegExp(getKeywords(keywords), 'gm'), css: 'keyword'}];
function getKeywords(str) {
    return '\\b' + str.replace(/ /g, '\\b|\\b') + '\\b';
}
    


2. 接下来我们需要通过JavaScript中的正则匹配来找到所有的匹配项。
for (var i = 0; i < regexList.length; i++) {
    var match = null;
    while ((match = regexList[i].regex.exec(source)) != null) {
        matchs.push({
            value: match[0],
            index: match.index,
            length: match[0].length,
            css: regexList[i].css
        });
    }
}
    


3. 然后对匹配项进行排序(按照其在原始代码中的顺序)
function sortCallback(m1, m2) {
    if (m1.index < m2.index)
        return -1;
    else if (m1.index > m2.index)
        return 1;
    else {
        return 0;
    }
}
matchs = matchs.sort(sortCallback);
    


4. 最后拿着这些排好顺序的匹配项,和原始代码进行整合就好了。


注意事项

在拿到排序后的匹配项列表后,还不能和原始代码整合,因为其中有重复项,比如:
    var keywords = 'abstract boolean break';
    
会找到如下匹配项:var,'abstract boolean break',abstract,boolean,break这五个匹配项,其中一个字符串,四个关键词。
对于这样的情况,我们还必须起初重复的匹配项。
function isInlcude(matchs, index) {
    var match = matchs[index];
    for (var i = index - 1; i >= 0; i--) {
        if (matchs[i]) {
            if (match.index > matchs[i].index && match.index < matchs[i].index + matchs[i].length) {
                return true;
            }
        }
    }
    return false;
}
for (var i = 0; i < matchs.length; i++) {
    if (isInlcude(matchs, i)) {
        matchs[i] = null;
    }
}
    



修改后的源代码

这里只处理JavaScript的情况,对于其他语言的代码,只需要定义不同的正则匹配表达式就行了。
function hightLight(source) {
    var keywords = 'abstract boolean break byte case catch char class const continue debugger ' +
  'default delete do double else enum export extends false final finally float ' +
  'for function goto if implements import in instanceof int interface long native ' +
  'new null package private protected public return short static super switch ' +
  'synchronized this throw throws transient true try typeof var void volatile while with';
    var regexList = [{ regex: new RegExp('/\\*[\\s\\S]*?\\*/', 'gm'), css: 'comment' },
    { regex: new RegExp('//.*$', 'gm'), css: 'comment' },
    { regex: new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"', 'g'), css: 'string' },
    { regex: new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'", 'g'), css: 'string' },
    { regex: new RegExp(getKeywords(keywords), 'gm'), css: 'keyword'}];
    var matchs = [], result = [];
    function getKeywords(str) {
        return '\\b' + str.replace(/ /g, '\\b|\\b') + '\\b';
    }
    function sortCallback(m1, m2) {
        if (m1.index < m2.index)
            return -1;
        else if (m1.index > m2.index)
            return 1;
        else {
            return 0;
        }
    }
    function isInlcude(matchs, index) {
        var match = matchs[index];
        for (var i = index - 1; i >= 0; i--) {
            if (matchs[i]) {
                if (match.index > matchs[i].index && match.index < matchs[i].index + matchs[i].length) {
                    return true;
                }
            }
        }
        return false;
    }
    function convertSpace(str) {
        return str.replace(/\t/g, "    ").replace(/ /g, " ");
    }
    for (var i = 0; i < regexList.length; i++) {
        var match = null;
        while ((match = regexList[i].regex.exec(source)) != null) {
            matchs.push({
                value: match[0],
                index: match.index,
                length: match[0].length,
                css: regexList[i].css
            });
        }
    }
    if (matchs.length) {
        matchs = matchs.sort(sortCallback);
        // 去除重复
        for (var i = 0; i < matchs.length; i++) {
            if (isInlcude(matchs, i)) {
                matchs[i] = null;
            }
        }
        var i = 0;
        for (var j = 0; j < matchs.length; j++) {
            var match = matchs[j];
            if (match) {
                if (match.index > i) {
                    result.push('<span>' + convertSpace(source.substr(i, match.index - i)) + '</span>');
                }
                result.push('<span class="' + match.css + '">' + convertSpace(match.value) + '</span>');
                i = match.index + match.length;
            }
        }
        if (i != source.length - 1) {
            result.push(convertSpace(source.substr(i)));
        }
    }
    var resultLines = result.join("").split("\n");
    var out = [], alt = false;
    out.push('<div class="dp-highlighter"><ol start="1" class="dp-c">');
    for (var i = 0; i < resultLines.length; i++) {
        out.push(alt ? '<li class="alt">' : '<li>');
        out.push('<span>');
        out.push(resultLines[i] === "" ? " " : resultLines[i]);
        out.push('</span>');
        out.push('</li>');
        alt = !alt;
    }
    out.push('</ol></div>');
    return out.join("");
}
$(function() {
    $("pre.js").each(function() {
        var $this = $(this);
        $this.hide().after(hightLight($this.html()));
    });
});
    


原文地址:https://www.cnblogs.com/sanshi/p/1539636.html