Hello World
Spiga

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

2009-12-15 15:38 by 老赵, 6896 visits

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

客户端着色的最大问题,在于很可能会出现着色错误,例如这段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;。不过您还真别说,这个丑陋的做法还有另一个效果,那就是对于某些同样自做聪明的阅读器或浏览器(大都是移动平台上的程序),代码片断也能显示正常——而之前的所有方式,在那些设备上都只会显示为一行。

真是可歌可泣。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

36 条回复

  1. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-15 15:44:00

    测试代码着色:

    int a/*//*
    Test/**/, b = 0;
    int c = 0;/*/*Test
    *//Test
    


    看来对注释处理不错。


    那么字符串呢?
    string s = @"//abc
    /*/123
    ";
    */
    


    看来用的是正则表达式,所以这种都难不倒。

    int i = 0;
    ((IList<List<int>>) o)[0].Add( i >> 4 );
    

  2. 疯子阿飞
    *.*.*.*
    链接

    疯子阿飞 2009-12-15 15:47:00

    有好的按语法分析来进行着色的算法吗?

  3. 老赵
    admin
    链接

    老赵 2009-12-15 15:55:00

    @疯子阿飞
    如果针对特定语言来写,可以有比现在更好的,但应该做不到VS般完美吧。

  4. 韦恩卑鄙 alias:v-zhewg
    *.*.*.*
    链接

    韦恩卑鄙 alias:v-zhewg 2009-12-15 16:03:00

    服务器上有a片 就自然着色了

    话说能不能找到vs的分析器类库的实现呢? 似乎不难

  5. 老赵
    admin
    链接

    老赵 2009-12-15 16:04:00

    @韦恩卑鄙 alias:v-zhewg
    你拿到分析方式其实也不够,难道在着色的时候还要引用一大堆类库吗?

  6. 韦恩卑鄙 alias:v-zhewg
    *.*.*.*
    链接

    韦恩卑鄙 alias:v-zhewg 2009-12-15 16:05:00

    Jeffrey Zhao:
    @韦恩卑鄙 alias:v-zhewg
    你拿到分析方式其实也不够,难道在着色的时候还要引用一大堆类库吗?


    嗯 我看不错 花哈哈哈

    “话说能不能找到vs的分析器类库的实现呢? 似乎不难 ” 其实是防止被你当成纯水故意加上去的蠢问题 重点在A片

  7. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-12-15 16:09:00

    老赵不是要推广F# 吗?
    期待老赵的F# 系列教程呢。。。

  8. 老赵
    admin
    链接

    老赵 2009-12-15 16:10:00

    @xiao_p
    难以下手,写不来啊,就怕写成抄书了啊……目前的确打算抄一本Scala的书,抄成F#的。

  9. 韦恩卑鄙 alias:v-zhewg
    *.*.*.*
    链接

    韦恩卑鄙 alias:v-zhewg 2009-12-15 16:22:00

    Jeffrey Zhao:
    @韦恩卑鄙 alias:v-zhewg
    你拿到分析方式其实也不够,难道在着色的时候还要引用一大堆类库吗?


    嗯 突然想起来 识别验证码ocr可以做成Web Service共享给别人
    专业着色也可以吧,只要有人买单

  10. 老赵
    admin
    链接

    老赵 2009-12-15 16:59:00

    @韦恩卑鄙 alias:v-zhewg
    专业着色,要求提供代码片断和相关程序集——至少是元数据。呼呼。

  11. 幸存者
    *.*.*.*
    链接

    幸存者 2009-12-15 18:11:00

    @Ivony...
    正则表达式无法处理嵌套的情况,所以恐怕不是简单的基于正则表达式。

  12. DiryBoy
    *.*.*.*
    链接

    DiryBoy 2009-12-15 18:18:00

    VS在真正掌管元数据,要是能在粘贴的时候拿到这些信息,也许能够解决问题。例如

    <span style="color: blue" class="code_keyword">

    那么就既能拥有一个默认的设置,以后博客皮肤换了直接改CSS,加上一个 !important 就行了,对于Rss阅读器的影响也不大。

  13. 老赵
    admin
    链接

    老赵 2009-12-15 18:21:00

    @DiryBoy
    我看过VS 2010的Editor的开发人员访谈,它们分了3层,最上面一层是WPF表现层,下方是一个immutable的模型层,给了我们需要的这些元数据。
    我现在在看VS的扩展文档,遇到好玩的东西总是忍不住要玩一下,呵呵。

  14. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-12-15 18:30:00

    幸存者:
    @Ivony...
    正则表达式无法处理嵌套的情况,所以恐怕不是简单的基于正则表达式。



    恰好字符串字面常量和注释都不是嵌套结构。

  15. Ariex
    *.*.*.*
    链接

    Ariex 2009-12-15 18:41:00

    现在还有东西是基于简单文字查找来实现着色的么?那样岂不是很磕碜……

  16. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-12-15 18:43:00

    假设要对类型名称着色的话,我们只需要分析出它是个类型就行了,不用真的判断它是不是有效的类型。不过这仅限于类型的声明语句,使用类型的地方仍然不足以判断。比如

    A.Foo.Bar

    到底A是命名空间呢,或者Foo是A的嵌套类呢,或者Foo是A的静态属性呢,或者Foo是对象A的属性呢………… 无从判断

  17. DiryBoy
    *.*.*.*
    链接

    DiryBoy 2009-12-15 18:44:00

    @Jeffrey Zhao
    能拿到的话,剩下的问题就很简单了……

  18. ::似水无痕::
    *.*.*.*
    链接

    ::似水无痕:: 2009-12-15 18:54:00

    博客换黑色背景?
    1、那么先在VS里面调整好,背景设为黑色,着色部分调整好,就可以继续使用Paste from Visual Studio了。不过以前的代码还是要修改的,不管是重新弄到VS里还是加样式都是要改的。
    2、直接修改code的背景是最好的,这样不管博客背景换成什么code着色方案都不用改,博客背景就要根据code背景来设置了,这样总比改以前的代码好多了。
    我想没人闲得没事黑白来回切换着玩

  19. 老赵
    admin
    链接

    老赵 2009-12-15 18:56:00

    @::似水无痕::
    嗯,我说得就是以前的代码。如果用class的话,只要改CSS就可以了,也只有如此才能来回切换着玩了。

  20. 幸存者
    *.*.*.*
    链接

    幸存者 2009-12-15 19:36:00

    如果是正则表达式,像下面这种情况应该无法正确着色才对:

    var str = "abc\"" + "def";
    

  21. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-12-15 20:02:00

    幸存者:
    如果是正则表达式,像下面这种情况应该无法正确着色才对:

    var str = "abc\"" + "def";
    


    最短匹配+1位lookbehind就能解决这个问题了……问题是有嵌套结构的东西不可能用正则表达式分析,自动机的能力限制问题。
    其它脑袋都说了,俺继续潜水……

  22. CHEN Samuel
    *.*.*.*
    链接

    CHEN Samuel 2009-12-15 22:37:00

    最近在弄毕业设计,导师让弄分布式结对编程,也想实现这个功能呢。

  23. 老赵
    admin
    链接

    老赵 2009-12-15 23:01:00

    @zzfff
    其实也不算太short,抓紧时间便是,呵呵。

  24. 老赵
    admin
    链接

    老赵 2009-12-15 23:01:00

    @CHEN Samuel
    分布式结队编程?

  25. zzfff[未注册用户]
    *.*.*.*
    链接

    zzfff[未注册用户] 2009-12-15 23:08:00

    Art is long, life is short
    真酷

  26. zzfff[未注册用户]
    *.*.*.*
    链接

    zzfff[未注册用户] 2009-12-15 23:40:00

    code name oslo离1.0不远了,Anders同学的compiler as a service承诺早日兑现,生活会美好很多,但跑题依旧

  27. 老赵
    admin
    链接

    老赵 2009-12-16 01:21:00

    DiryBoy:
    VS在真正掌管元数据,要是能在粘贴的时候拿到这些信息,也许能够解决问题。例如

    <span style="color: blue" class="code_keyword">

    那么就既能拥有一个默认的设置,以后博客皮肤换了直接改CSS,加上一个 !important 就行了,对于Rss阅读器的影响也不大。


    莫非css里的!important可以覆盖元素上的style呀?要是这样就再好不过了。

  28. 银河
    *.*.*.*
    链接

    银河 2009-12-16 08:04:00

    使用 Paste from Visual Studio 插件进行着色的话,没法显示行号。不知大家有没有办法解决?

  29. 老赵
    admin
    链接

    老赵 2009-12-16 09:04:00

    @银河
    修改VSPaste,今天我就会写文章谈这件事情,hoho

  30. lyan
    *.*.*.*
    链接

    lyan 2009-12-16 09:18:00

    测试代码着色

        /// <summary>
        /// 字段属性
        /// </summary>
        [AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
        public class FieldNameAttribute : Attribute
        {
            /// <summary>
            /// 字段名
            /// </summary>
            public string Name
            {
                get;
                set;
            }
            /// <summary>
            /// 所属表名
            /// </summary>
            public string TableName
            {
                get;
                set;
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="fieldname">字段名</param>
            public FieldNameAttribute(string fieldname)
            {
                Name = fieldname;
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="tablename">表名:实体类名</param>
            /// <param name="fieldname">字段名</param>
            public FieldNameAttribute(string tablename, string fieldname)
            {
                TableName = tablename;
                Name = fieldname;
            }
        }
    

  31. dudu
    *.*.*.*
    链接

    dudu 2009-12-16 10:19:00

    由于博客园的后台的某个编辑器,总是“自做主张”地将<pre />内的代码进行“优化”,把多个空白字符替换成一个。


    某个编辑器就是CuteEditor,而TinyMCE就不会。
    所以在使用CuteEditor时,我们采用的方法也是多个空格替换为&nbsp;,但带来的问题是着色后的代码的字节数大大增加。

  32. 张荣华
    *.*.*.*
    链接

    张荣华 2009-12-16 10:28:00

    Jeffrey Zhao:
    @银河
    修改VSPaste,今天我就会写文章谈这件事情,hoho


    期待老赵的这篇文章。

  33. 老赵
    admin
    链接

    老赵 2009-12-16 10:52:00

    dudu:

    由于博客园的后台的某个编辑器,总是“自做主张”地将<pre />内的代码进行“优化”,把多个空白字符替换成一个。


    某个编辑器就是CuteEditor,而TinyMCE就不会。
    所以在使用CuteEditor时,我们采用的方法也是多个空格替换为&nbsp;,但带来的问题是着色后的代码的字节数大大增加。


    这种编辑器删了算了,呵呵。

  34. DiryBoy
    *.*.*.*
    链接

    DiryBoy 2009-12-16 12:26:00

    Jeffrey Zhao:
    莫非css里的!important可以覆盖元素上的style呀?要是这样就再好不过了。


    可以的。
    .keyword {
      color: red !important
    }
    

    <span style="color:blue" class="keyword">var</span>
    

    这样 var 就变成红色了。

  35. ☆用心生活☆
    *.*.*.*
    链接

    ☆用心生活☆ 2009-12-22 11:17:00

    删是不可以的,人各有爱啊,青菜萝卜各所喜好,HOHO。

  36. 老赵
    admin
    链接

    老赵 2009-12-22 11:35:00

    @☆用心生活☆
    好吧,如果真有人喜欢那个编辑器的话……

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我