代码改变世界

浅谈代码着色(下):服务器端着色

2009-12-15 15:38  Jeffrey Zhao  阅读(7040)  评论(36编辑  收藏  举报

上篇文章谈了客户端着色,而现在自然就来讨论服务器端着色了。先下个定义:我在这里谈的“服务器端着色”,是指直接从服务器端输出着色效果的做法(与“客户端着色时”输出纯代码文本相对)。至于这个着色效果是如何获得的,例如是由另一个用户直接提供的,还是用户提供纯代码文本,而用服务器端逻辑“着色”,在这里就统称为“服务器端”着色了。不过接下去的讨论,我们还是会作一些区分。

客户端着色的最大问题,在于很可能会出现着色错误,例如这段C#代码:

 
public string Foo
{
    get
    {
        //*
        int set = 0;
        int value = 0;
        string yield = "/*" + set + value + "*/";
        //*/
    }
}

或者这段VB.NET:

 
Dim [Dim] As My.Dim
Dim [REM] = "REM asdf"" 'Dim a As Dim""" 'Dim a As String
Dim x = <Dim Rem="Dim '">'HaHa "</Dim>

很明显这两段代码的着色都有问题(如C#的set关键字和VB.NET的XML Literal)——它们是脑袋在上篇文章的评论中给出的两个例子,他解释道:

基于简单文本查找的代码着色很可能会在注释,字符串之间混杂嵌套的地方出错。比如连续两个/* */,可能会匹配到较远的一个。还有//和/*等同时出现的情况。现在的很多语言都有两种以上的注释格式。同样,字符串的""和''还有转义,都不是简单替换算法能解决的。

基于词法的着色无法解决限定标识符的问题。比如DataColumn.ReadOnly中,ReadOnly是VB的关键字,但这里是被DataColumn限定的标识符,不应该变成蓝色。一般限定名称的解析不是在词法分析阶段,所以能够解决这个问题的代码着色器极少。

另外想要达到将类型名称着色(像VS那样)必须做完整的语法分析。估计也不是现在任何着色器能做到的。

同样,您可以尝试着将代码放入Editplus或Notepad++等工具中查看,错误依旧。对于这个问题,您可以简单的理解为:由于进行“客户端着色”时缺少足够的缺少信息,因此在很多情况下着色会出现偏差。而由于VS本身使用完整的语法分析,因此它的着色效果自然无可挑剔:

class Program
{
    public string Foo
    {
        get
        {
            //*
            int set = 0;
            int value = 0;
            string yield = "/*" + set + value + "*/";
            //*/
        }
    }
}

由于直接使用了VS的着色规则,因此这里的set关键字也正确了,Program作为类名也有漂亮的颜色,这样整段代码看上去非常赏心悦目,它也是我在博客上用的着色方式。我使用Windows Live Writer写博客,配合Paste from Visual Studio插件,可以直接从Visual Studio里复制一段代码插入到文章内容中,例如对于上面的代码它便会生成这样的HTML:

<pre class="code"><span style="color: blue">class </span><span style="color: #2b91af">Program
</span>{
    <span style="color: blue">public string </span>Foo
    {
        <span style="color: blue">get
        </span>{
            <span style="color: green">//*
            </span><span style="color: blue">int </span>set = <span style="color: brown">0</span>;
            <span style="color: blue">int </span>value = <span style="color: brown">0</span>;
            <span style="color: blue">string </span>yield = <span style="color: #a31515">&quot;/*&quot; </span>+ set + value + <span style="color: #a31515">&quot;*/&quot;</span>;
            <span style="color: green">//*/
        </span>}
    }
}</pre>

很长,还好我们平时不会去阅读这类HTML源文件表现的具体内容。我们可以发现,在这段HTML中,所有的颜色都直接出现在代码中。这么做的优点在于即便是别人通过RSS订阅了您的文章,再它的阅读器里也可以看到美观而工整的着色样式。当然,它也有一些缺点。而它最大的问题便在于样式和内容直接耦合在了一起。也就是说,这么做的显示效果其实严重依赖它所存在的环境。假设,我忽然有一点想要把博客的风格切换成很酷的黑色,这样代码块的阅读体验就十分堪忧了。同样,如果读者的RSS阅读器使用了某种花哨的风格……

要解决这个问题并不容易。一个可以尝试的做法是在HTML中使用class来标记元素,而并非直接将颜色代码写在HTML当中。例如:

<span class="keyword">class </span><span class="type">Program</span>

由于我们只是标记了HTML元素的“种类”,因此代码的样式便可以自由定义了。即便是我们换了一个新的环境,那提供另一套CSS样式即可。可惜的是,这种做法在RSS读者那边又失效了。当然,如果您也可以在服务器端的逻辑里为代码快进行重新着色……对于完美主义者,我们还是应该表示尊敬的。

可惜的是,我在博客里还是直接内嵌颜色代码,而不是使用class+CSS的方式。第一个原因是由于我可能不会对博客样式有“质的调整”,但更重要的原因是……插件没有直接支持,可能是VS直接提供了着色结果,而不是“结构化数据”。当然,我们其实也可以重新写一个插件,从特定颜色代码“识别回”类型(其实普通的查找替换应该也足够了)。既然有Paste from Visual Studio插件作为示例,做到这点其实应该也是挺容易的。

好吧,如果真的某一天我需要把博客切换为黑底,又该怎么办呢?没办法了,写一个程序,利用博客园提供的Weblogs API,批量替换一下吧。

回到文章开头,我谈到“服务器端着色”的另一种情况,是使用服务器端的逻辑将代码块进行高亮,这种做法大都出现在处理wiki标记或BBCode时进行。很可惜,这也是在一种缺少上下文信息的环境下进行着色,与“客户端着色”一样,难以出现完美的着色效果。最后可能值得一提的是,我在某段不算太长的时间内还是用过一种“不堪入目”的着色方式:

<pre class="code"><span style="color: blue">public string </span>Foo<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue">get<br />&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: blue">return </span><span style="color: #a31515">&quot;bar&quot;</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}</pre>

感觉如何?其实它的最终结果只是如此简单:

public string Foo
{
    get
    {
        return "bar";
    }
}

由于博客园的后台的某个编辑器,总是“自做主张”地将<pre />内的代码进行“优化”,把多个空白字符替换成一个,于是换行没了,缩进也没了。于是,我只能手动将换行替换为<br />,将多个空格替换为&nbsp;。不过您还真别说,这个丑陋的做法还有另一个效果,那就是对于某些同样自做聪明的阅读器或浏览器(大都是移动平台上的程序),代码片断也能显示正常——而之前的所有方式,在那些设备上都只会显示为一行。

真是可歌可泣。