Saturday, December 28, 2013

Syntax Highlighter 使用方法

作为一个开发人员,经常需要在一些技术blog中经常想要插入一些代码段。SyntaxHighlighter是一个开源的js库,作用是可以仅仅通过JavaScript将Html中的代码段以与IDE类似的样式呈现出来。

Syntax Highlighter的特点:
  1. 完全以client端脚本实现,也就是说不需要链接服务器,仅仅通过在浏览器中执行JavaScript即可完成功能。这使得Syntax Highlighter可以被方便的集成到blogger上,尽管自己的blog是host在blogger.com上的。
  2. 支持多种Theme。通过引入不同的css文件,可以方便的在各种Theme之间切换。
  3. 支持多种编程语言。它基本涵盖了所有目前主流的开发语言,可以通过制定brush,来告知Syntax Highlighter以何种语言模板来呈现你的代码段。

使用方法
首先,引入Syntax Highlighter需要的CSS和JavaScript:
<link rel="stylesheet" href="styles/shCore.css">
<link rel="stylesheet" href="styles/shThemeDefault.css">
<script type="text/javascript" src="scripts/shCore.js"></script>
接下来需要调用SyntaxHighlighter的初始化方法,控制SyntaxHighlighter对页面的代码段进行美化:
<script>
    SyntaxHighlighter.all();
</script>
注意这个方法可以在任意的地方调用,不需要等待文档加载完毕。可能会觉得奇怪,如果文档没有加载完毕,如何来确定美化哪些界面元素呢?通过查看源码,我们可以发现,其实原因特别简单:在shCore.js中有这样一段代码:
/**
 * Main entry point for the SyntaxHighlighter.
 * @param {Object} params Optional params to apply to all highlighted elements.
 */
all: function(params)
{
    attachEvent(
        window,
        'load',
        function() { sh.highlight(params); }
    );
}
这也就是调用all方法执行的代码。从中可以看出,尽管我们在外部没有等到文档加载完毕,但是在all方法内部,实际上已经是将程序的入口方法放在了load事件中,也就是文档加载完毕后再执行。

其实我们并没有引入足够的js文件,我们还缺少一系列brush文件,这个一会儿再说。

接下来我们要告诉Syntax Highlighter代码段在哪里。想要达成这一点有两种方法:
  1. 使用<pre>...</pre>元素对代码段进行包装:很简单,将代码段放在一对<pre>标签中,并且给pre标签加入class属性,用来指定一些参数。其中必须的参数的brush——用来指明这段代码是何种语言,之后SyntaxHighlighter就会使用这种语言的模板,对这段代码进行呈现。例:
    <pre class="brush: js">
        /**
         * SyntaxHighlighter
         */
        function foo()
        {
            if (counter <= 10)
                return;
            // it works!
        }
    </pre>
    
    上面的代码将呈现为如下的结果:
    /**
     * SyntaxHighlighter
     */
    function foo()
    {
        if (counter <= 10)
            return;
        // it works!
    }
    
    使用这种方法有一个弊端,那就是必须要对代码中的HTML特殊字符进行转义,比如上例的<就需要使用HTML Escape Tool预先进行处理。
  2. 使用<script><![CDATA[…]]></script>元素对:其实是将代码段包裹在<script><![CDATA[…]]></script>里面。同样需要指定script元素的class属性来向SyntaxHighlighter传递参数,brush属性同样是必要的。另外这里需要指定script的type属性为syntaxHighlighter。例:
    <script type="syntaxhighlighter" class="brush: js"><![CDATA[
    /**
     * SyntaxHighlighter
     */
    function foo()
    {
        if (counter <= 10)
            return;
        // it works!
    }
    ]]></script>
    
    上面的代码将呈现如下结果: 这种方法的好处是,我们不需要对代码段中的HTML特殊字符进行转换了。
    但是这种方法也有弊端,多数的RSS阅读器会过滤掉script标签,因为如果你的站点会被RSS阅读器读取,那么建议使用第一种方式。

引入shBrushXxx.js
上面留下了关于Brush的问题。比如上面两个例子中我们需要渲染的都是JS代码,因此我们需要引入shBrushJScript.js. 所以同样将下面的语句放在head标签内:
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
到此为止我们保存Template,和我们post,刷新页面即可看到被渲染过的代码段了。

shAutoloader.js
大家可能已经注意到了,这里存在一个性能问题。通常我们的网站是动态的,也就是说多个页面使用同一个模板,通过填充数据的方式,来生成不同的页面。那么这里问题就出现了,在模板中我们必须预先知道这个页面中需要显示哪种类型的代码段。比如,需要显示Java 代码,就需要引入shBrushJava.js;需要显示C#代码段,就需要引入shBrushCSharp.js。如果说在编写模板页的时候,不能确定会显示哪些类型的代码段时,事情就有些麻烦了。一种保守的解决方案是把所有SyntaxHighlighter支持的编程语言的Brush.js文件都引入。但是这无疑是一种浪费,因为,很有可能显示的页面只用到了其中一个Brush.js。
如何来解决上面的问题呢,在SyntaxHighlighter 3以前是没法解决的。SyntaxHighlighter 3中提供了一种优化的方式来解决上面的问题——shAutoloader.js
这个js文件的功能是自动加载需要的Brush文件。听起来非常酷吧~在实际使用的时候,并不是太酷。因为官方的文档对这一部分描述的太过于简单,但是实际上想要使用这个功能,有许多地方需要注意,我是通过一点点实验和查看源码才理解了使用的方法。
官方文档上说,使用shAutoloader只需要引入shAutoloader.js。然后调用SyntaxHighlighter.autoloader 方法添加brush alias 到 brush.js文件的映射即可。
<script>
    function path() {
        var args = arguments;
        var result = [];
        for (var index in args) {
            args[index][args[index].length - 1] = args[index][args[index].length - 1].replace('@', 'scripts/');
            result.push(args[index]);
        }
        return result;
    }
    SyntaxHighlighter.autoloader.apply(null, path(
        ['applescript', '@shBrushAppleScript.js'],
        //此处省略大约22行
        ['xml', 'xhtml', 'xslt', 'html', '@shBrushXml.js']
    ));
    SyntaxHighlighter.all();
</script>
这段代码没有错,按照这种方法的确可以让代码选正确显示。但是官方文档里面忽略了一个问题——这段脚本放在哪执行呢?必须是文档加载之后么?大家可能还记得SyntaxHighlighter.all方法可以在任一地方调用,那么SyntaxHighlighter.autoloader方法是不是也一样呢?
经过查看源代码,和多次的debug,我现在可以给出准确的答案:
  1. SyntaxHighlighter.autoloader方法必须在文档加载之后进行调用。原因是autoloader的代码中没有把要执行的逻辑attach到load事件里面,所以它不具有SyntaxHighlighter.all方法那种灵活性。如果autoloader方法在文档加载之前执行了,那么你不会收到任何错误提示,SyntaxHighlighter会以为文档中不存在任何代码段,进而不会进行任何美化操作;
  2. SyntaxHighlighter.all方法必须要在SyntaxHighlighter.autoloader方法之后调用。如果不这样做,将收到一个找不到相应Brush的错误。这个很容易理解吧,因为Brush要在autoloader方法执行之后才会被加载,autoloader方法还没有执行呢,当然就没有Brush啦。
总的来说,Syntax Highlighter是个好东西,autoloader我却不敢恭维。因为太依赖于使用者的调用方式了,这样是不健壮的。

那么就像上面所说,如何使我的代码段在文档加载完毕后执行呢?这有好多种方式了:
  1. 将脚本段放在</body>之前,保证在执行脚本的时候body中的元素已经加载完毕;
  2. 和all的实现方法一样,将自己的方法attach到document的load事件上;
  3. 使用jQuery,在文档准备好后执行你的方法。
这里不讨论哪种方法更好,我使用了第三种,因为页面可能会有别的功能使用到jQuery,所以感觉第三种更适合我。为了方便,我把代码也放上来:
<head>
    <link rel="stylesheet" href="styles/shCore.css">
    <link rel="stylesheet" href="styles/shThemeDefault.css">
    <script type="text/javascript" src="scripts/shCore.js"></script>
    <script type="text/javascript" src="scripts/shAutoloader.js"></script>
    <script src="jquery-1.10.2.min.js"></script>
    <script>
        function path() {
            var args = arguments;
            var result = [];
            for (var i=0;i<args.length;i++) {
                args[i][args[i].length - 1] = args[i][args[i].length - 1].replace('@', 'js/syntaxhighlighter/scripts/');
                result.push(args[i]);
            }
            return result;
        }

        SyntaxHighlighter.config.bloggerMode = true;
        SyntaxHighlighter.defaults["toolbar"] = false;

        $(function() {
            SyntaxHighlighter.autoloader.apply(null, path(
                ['applescript', '@shBrushAppleScript.js'],
                ['actionscript3', 'as3', '@shBrushAS3.js'],
                ['bash', 'shell', '@shBrushBash.js'],
                ['coldfusion', 'cf', '@shBrushColdFusion.js'],
                ['cpp', 'c', '@shBrushCpp.js'],
                ['c#', 'c-sharp', 'csharp', '@shBrushCSharp.js'],
                ['css', '@shBrushCss.js'],
                ['delphi', 'pascal', '@shBrushDelphi.js'],
                ['diff', 'patch', 'pas', '@shBrushDiff.js'],
                ['erl', 'erlang', '@shBrushErlang.js'],
                ['groovy', '@shBrushGroovy.js'],
                ['java', '@shBrushJava.js'],
                ['jfx', 'javafx', '@shBrushJavaFX.js'],
                ['js', 'jscript', 'javascript', '@shBrushJScript.js'],
                ['perl', 'pl', '@shBrushPerl.js'],
                ['php', '@shBrushPhp.js'],
                ['text', 'plain', '@shBrushPlain.js'],
                ['py', 'python', '@shBrushPython.js'],
                ['ruby', 'rails ror rb', '@shBrushRuby.js'],
                ['sass', 'scss', '@shBrushSass.js'],
                ['scala', '@shBrushScala.js'],
                ['sql', '@shBrushSql.js'],
                ['vb', 'vbnet', '@shBrushVb.js'],
                ['xml', 'xhtml', 'xslt', 'html', '@shBrushXml.js']
            ));
            SyntaxHighlighter.all();
        });
    </script>
</head>

切换Theme
SyntaxHighlighter提供了许多Theme,作为用户,我们可以通过引入不同的css文件来方便的切换主题。上面我们引用了一个shThemeDefault.css这个css文件,可以将其替换为不同主题的css文件。比如shThemeEclipse.css,就会让代码段被渲染成Eclipse的风格。

配置Syntax Highlighter
Syntax Highlighter提供了很多可供配置的参数,有三种方式对Syntax Highlighter进行配置:
  1. 全局属性的配置
    比如想要把Syntax Highlighter集成到Blogger.com上,那么就需要配置这个全局属性:
    SyntaxHighlighter.config.bloggerMode = true; 
    
  2. 局部参数配置
    比如想要把代码段旁边的行号去掉,可以这样配置:
    <pre class="brush: xxx; gutter: false;"> 
    
  3. 默认局部参数配置
    其实就是对局部参数配置的一个补充,比如想要把所有的代码段的gutter都设置为false,那么可以这样做:
    SyntaxHighlighter.defaults["gutter"] = false;
    
大家可能也注意到了,代码段的右上角有个比较讨厌的"?",如何去掉呢?
function toolbarFun() {
    //example
}
针对这一点Syntax Highlighter提供了配置项,我们就可以通过修改默认局部参数配置来达到隐藏"?"的目的:
SyntaxHighlighter.defaults["toolbar"] = false;
Syntax Highlighter提供的配置项还有很多,这里就不一一列举了,详细的参数列表请看这里

因为我也是刚刚开始使用Syntax Highlighter,如有不准确的地方,敬请指正。

1 comment :