中文 WordPress 上看到一篇 HowTo: 显示彩色代码,里面介绍了可以用 WordPress 的一个插件 Syntax Highlighter with Enscript 进行代码的语法高亮显示。不过他们只简单的介绍了如何语法高亮显示 PHP 代码。事实上,借助于 GNU Enscript 的强大功能,这个插件可以让很多程序设计语言的代码语法高亮显示。

看了一下 Syntax Highlighter 的源代码,发现对于 PHP 代码,它调用 PHP 的 highlight_string() 函数来进行语法高亮的格式化,因此没有其他需求。而对于其他程序设计语言,则需要调用 Web 主机上安装的 GNU Enscript 来进行语法高亮格式化。

好在我的 Blog 是放在自己管理的机器上的,安装软件很容易。在 Free Software Foundation 的 GNU Enscript 页面上下载了 Win32 Binaries Zip 包,解压到准备安装的目录,并且为了能方便 Syntax Highlighter 和我们自己使用 Enscript,还要把这个目录下的 bin 子目录添加到系统环境变量 PATH 里。OK,用如下格式贴一段 Delphi 代码测试一下。

<pre lang="delphi">
program Hello;
var
  I: Integer;
begin
  for I := 0 to 9 do
    Writeln('Hello');
end.
</pre>

Good,代码正常的语法高亮显示了。

但是,代码前面多了个大大的 "(stdin)"。猜测应该是 Enscript 产生了不必要的输出。试着手工运行 Enscript 产生代码,果然这个由 <H1> 修饰的输入文件名 (stdin 表示控制台程序的标准输入) 是由 Enscript 输出的。看了一下 Enscript 的命令行参数,发现其中有几个跟 header 有关的参数,遂逐一尝试,但都没有任何效果,讨厌的文件名标题仍然还在。同时发现即使是用 Syntax Highlighter 调用 Enscript 所用的参数运行 Enscript,产生的 HTML 代码也是一个完整的 HTML 页面的代码,因此 Syntax Highlighter 肯定会对 Enscript 输出的 HTML 进行处理。查看其源代码,果然发现在 hilight_enscript() 函数的最后有这两行代码

$code = eregi_replace("^.*<PRE>\n",  '', $code);
$code = eregi_replace("\n</PRE>.*$", '', $code);

这两行代码使用正则表达式强大的模糊搜索替换能力将 Enscript 输出的 HTML 中 <PRE> 标签之前和 </PRE> 标签之后的内容都去掉。从正则表达式语法上,我看不出任何有问题的地方。查 PHP 手册、Google 搜索,换用 preg_replace() 函数,结果引起更多的问题,又去查 Perl 手册中正则表达式的部分…… 搞了好久才发现是因为那个我并不陌生的 Linux 和 Windows 文本文件换行符不一样的问题。\n 明显就是 Linux 的换行符,而运行于 Win32 平台的 Enscript 产生的 HTML 换行符自然是 \r\n,从而导致第一行代码根本没有产生作用。OK,为了这个插件在 Linux 和 Windows 主机上都能正常使用,再加一句,改成这样

$code = eregi_replace("^.*<PRE>\n",  '', $code);
$code = eregi_replace("^.*<PRE>\r\n",  '', $code);
$code = eregi_replace("</PRE>.*$", '', $code);

最后一行的 \n 被我去掉了是因为这样产生的 HTML 源代码更美观并且不会影响显示效果。

另外查看 Syntax Highlighter 源代码还附带发现一出问题,在这段代码中

// FIXME: We are hardcoding the path to the temporary file name
// here. It needs to be changed to be system independent.
$file = tempnam('/tmp', '_syntax');
$handle = fopen($file, 'w');
fwrite($handle, $code);
fclose($handle);

$argv .= ' '.escapeshellcmd($file).' 2>&1 ';

作者说他对临时文件路径进行了硬编码,事实上这个硬编码不会出问题,因为 tempnam() 函数的特性是如果目录不存在则自动在系统的临时目录中建立文件。反倒是最后一行中的 escapeshellcmd() 函数会把 Windows 目录中的反斜杠 \ 替换成空格,导致 Enscript 无法正确得到临时文件路径。简单的把这个函数去掉,让 $file 变量直接参与字符串连接就行了。这个问题只在 PHP 版本低于 4.3 时出现,因为在新版的 PHP 中 Syntax Highlighter 会使用管道而不是临时文件来给 Enscript 提供输入。

OK, mission complete!

Tags: ,

Leave a Reply