在图灵社区使用 Markdown 写文章时,如果在一段文字的每行开头加上四个空格,或者一个制表符(Tab),这段文字就会被视为程序代码。这样,就会自动识别所用的编程语言,进行代码染色,语法高亮显示。但是,如果这段程序很长的话,就有两个小问题:

  1. 每行的开头要加上空格或制表符,很麻烦。
  2. 如果要显示行号的话,就更麻烦了。

因此,我用 C# 语言写了小程序,建设一个 ASP.NET 4 网站来解决上述两个麻烦:

1

2

3

在这个网页中:

  • Line Count 复选框表示是否需要加上行号。
  • Prefix 中的的 SpaceTab 无线按钮让你选择每行开头是增加空格还是制表符。
  • Prefix Count 文本框让你输入缩进的层次。默认是缩进一层 。但是如果遇到在有序列表或无序列表中的程序代码,就需要缩进两层,甚至更多层了。

这个网站的总体结构如下所示:

4

网站的配置文件 Web.config 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
    <globalization requestEncoding="utf-8" responseEncoding="utf-8" />
  </system.web>
</configuration>

网站的 Web 页面文件 CodeFormat.aspx 如下所示:

<%@ Page validateRequest="false" Language="C#" inherits="Skyiv.Ben.Web.CodeFormatPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  <title>Code Format</title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:Button Onclick="Submit" Text="Submit" Runat="Server" />
  <span style="background-color:LightBlue;">
    <asp:CheckBox Id="chkLineCount" Text="Line Count" Checked="True" Runat="Server" />
    &nbsp;
  </span>
  &nbsp;
  <span style="background-color:LightBlue;">
    &nbsp;Prefix:
    <asp:RadioButton Id="rbnSpace" Text="Space" Checked="True"
      GroupName="Prefix" Runat="Server" />
    <asp:RadioButton Id="rbnTab" Text="Tab"
      GroupName="Prefix" Runat="Server" />
    &nbsp;
  </span>
  &nbsp;
  <span style="background-color:LightBlue;">
    &nbsp;Prefix Count:
    <asp:TextBox Runat="Server" Id="tbxLevel" Text="1" Columns="2" MaxLength="1" />
    &nbsp;
  </span>
  <hr />
  <div>
  <asp:TextBox Runat="Server" Id="tbxInput" Wrap="False"
    TextMode="MultiLine" Columns="80" Rows="10" />
  <br />
  <asp:TextBox Runat="Server" Id="tbxOutput" ReadOnly="True" Wrap="False"
    TextMode="MultiLine" BackColor="LightBlue" Columns="80" Rows="10" />
  </div>
  </form>
</body>
</html>

以及对应的后台 C# 代码 CodeFormat.aspx.cs:

 1: using System;
 2: using System.IO;
 3: using System.Web;
 4: using System.Web.UI;
 5: using System.Web.UI.WebControls;
 6: using Skyiv.Utils;
 7: 
 8: namespace Skyiv.Ben.Web
 9: {
10:   public class CodeFormatPage : Page
11:   {
12:     protected TextBox tbxInput;
13:     protected TextBox tbxOutput;
14:     protected TextBox tbxLevel;
15:     protected CheckBox chkLineCount;
16:     protected RadioButton rbnTab;
17:     
18:     protected void Page_Load(object sender, EventArgs e)
19:     {
20:       tbxOutput.Text = string.Format(" OS: {1} ({2}-bit){0}CLR: {3}",
21:         Environment.NewLine, Environment.OSVersion,
22:         Environment.Is64BitOperatingSystem ? 64 : 32,
23:         Environment.Version);
24:     }
25: 
26:     protected void Submit(object sender, EventArgs e)
27:     {
28:       var writer = new StringWriter();
29:       new CodeFormat(new StringReader(tbxInput.Text),
30:         writer).Run(chkLineCount.Checked, rbnTab.Checked, GetLevel(tbxLevel.Text));
31:       tbxOutput.Text = writer.ToString();
32:     }
33:     
34:     int GetLevel(string str)
35:     {
36:       int n;
37:       if (!int.TryParse(str, out n)) n = 1;
38:       return Math.Min(5, Math.Max(0, n));
39:     }
40:   }
41: }

上述程序中:

  • 第 34 至 39 行的 GetLevel 方法读取 Prefix Count 文本框中的缩进层次,返回结果限制在 0 到 5 之间。
  • 第 26 至 32 行的 Submit 方法在 Web 页面中的 Submit 按钮被点击时被调用。
  • 第 29 至 30 行调用 CodeFormat 类的 Run 方法对程序代码进行格式化(加行号、行首空格等)。

下面就是 CodeFormat 类的源程序代码 CodeFormat.cs:

 1: using System;
 2: using System.IO;
 3: using System.Collections.Generic;
 4: 
 5: namespace Skyiv.Utils
 6: {
 7:   sealed class CodeFormat
 8:   {
 9:     TextReader reader;
10:     TextWriter writer;
11: 
12:     public CodeFormat(TextReader reader, TextWriter writer)
13:     {
14:       this.reader = reader;
15:       this.writer = writer;
16:     }
17:   
18:     public void Run(bool hasCount, bool isTab, int level)
19:     {
20:       Write(Read(), hasCount, isTab, level);
21:     }
22:     
23:     List<string> Read()
24:     {
25:       var lines = new List<string>();
26:       for (string s; (s = reader.ReadLine()) != null; ) lines.Add(s);
27:       return lines;
28:     }
29:     
30:     void Write(List<string> lines, bool hasCount, bool isTab, int level)
31:     {
32:       var prefix = "".PadLeft((isTab ? 1 : 4) * level, isTab ? '\t' : ' ');
33:       var format = "{0}" + (hasCount ? "{1," +
34:         lines.Count.ToString().Length + "}: " : "") + "{2}";
35:       var count = 0;
36:       foreach (var line in lines)
37:         writer.WriteLine(format, prefix, ++count, line);
38:     }
39:   }
40: }

上述程序中:

  • 第 9 至 10 行的 TextReaderTextWriter 分别用于读取数据和输出格式化后的结果,这两个类是抽象基类。
  • 在这个网站中,是使用 StringReaderStringWriter 派生类,对应于 Web 页面的 tbxInputtbxOutput 文本框。
  • 如果使用 StreamReaderStreamWriter 派生类,就可以从输入流读取数据,写到输出流中。
  • 如果使用 Console.InConsole.Out,就可以从标准输入读取数据,写到标准输出。
  • 第 23 至 28 行的 Read 方法读取数据到内存的 List<string> 数据结构中。
  • 第 30 至 38 行的 Writer 方法将内存中的数据格式化后写出去。
  • 第 32 行根据 isTablevel 参数决定程序代码数据每行的前缀。
  • 第 33 至 34 行根据 hasCount 参数决定行号的内容。
  • 第 34 行的 lines.Count.ToString().Length 是行号所占的宽度。
  • 第 36 至 37 行的循环逐行格式化数据。

最后是 Makefile:

CSC = dmcs
DLL1 = -r:System.Web.dll

../bin/CodeFormat.dll: CodeFormat.aspx.cs CodeFormat.cs
    $(CSC) -out:$@ -t:library $(DLL1) CodeFormat.aspx.cs CodeFormat.cs

有了上面的源程序后,执行 make 命令编译整个网站:

src$ make
dmcs -out:../bin/CodeFormat.dll -t:library -r:System.Web.dll CodeFormat.aspx.cs CodeFormat.cs

这就大功告成了。

评论

推荐 9
看这热情,把图灵社区开源吧 :D
UP!!!!!!!!!!! –  2gua 2013-03-24 20:36
你推荐一下这条评论,就会 UP!!!!!! 上去。 –  黄志斌 2013-03-24 20:57
同意~开源万岁~ –  王政 2013-03-24 22:11
高人很多啊。 –  谢工在百度 2013-03-24 22:35
支持支持支持 –  颜海镜 2014-01-20 09:47

推荐 5
强插行号是不符合语义化原则的,建议用 CSS3 的计数器功能(counter(line)):http://www.w3.org/TR/css3-content/#introduction
不错,最好是不要强插行号,但是在需要时又能够显示行号。有些网站的语法高亮插件能够实现这点。您说的 CSS3 的计数功能能够在 Markdown 中使用吗?您能够写篇文章阐述一下吗?以便我们学习使用。谢谢! –  黄志斌 2013-03-25 14:30
行号是 CSS3 新增的重要格式伪元素,只要在样式表加上文档里 pre 那两行就可以了(可惜目前是草案阶段,浏览器普遍不支持),不需要改变用 Markdown 的习惯。我建议的方案是:样式 pre.prettyprint{counter-reset:line;}span.linestart::before{counter-increment:line;content:counter(line)': ';} 脚本(高亮之后再执行) var pres=document.getElementsByClassName('prettyprint');for(var i=0;i<pres.length;i++){pres[i].innerHTML='<span class="linestart" />'+pres[i].innerHTML.replace(/\n/g,'\n<span class="linestart" />')} –  xslidian 2013-03-25 20:00
看不懂。还要执行脚本?好象我们写文章的人没有办法在 Markdown 中作到这点吧?不知图灵社区的管理员有木有办法? –  黄志斌 2013-03-26 09:35
不需要作者改变习惯。本地调试可以通过 notepad "%localappdata%\Google\Chrome\User Data\Default\User StyleSheets\Custom.css" 编辑样式表,浏览器按 F12 进 Console 执行脚本 –  xslidian 2013-03-26 15:10
那就是需要图灵社区的系统管理员在服务器上进行相应的设置了。是这样吗? –  黄志斌 2013-03-26 19:23
可以要求管理员设置,也可以把我上面的脚本存为书签(bookmarklet,链接由 javascript: 打头),一点就生效~ –  xslidian 2013-03-26 21:43

推荐 4
大家来个专题吧,用自己熟悉的语言实现之。
C 语言在格式化程序代码时的程序语句应该和 C# 差不多。就是不知道要创建一个网站在 C 语言中要怎么做。 –  黄志斌 2013-03-24 21:04
做这个,不一定要网站。 –  2gua 2013-03-25 08:20
如果不要网站的话,只要正文中最后一个 C# 程序就差不多了。做网站是为了方便对程序代码进行格式化,只要打开浏览器就行了,不用下载和安装程序,不在自己的电脑上也可以用。 –  黄志斌 2013-03-25 08:26

推荐 3
前几天我们在讨论Manning图书中的代码注释时,还提到加行号的事,因为书中是用标号括起几行代码,然后正文中加以解释的。用Markdown的代码只能将标号加注在某一行。如果有行号,就可以用文字说明了。

有个问题,如果所有代码都用行号标注,会不会反而影响读者的阅读?尤其是小段代码。

只有文字说明中需要用到行号的代码才标行号,其余的代码可以不标等号。如我这篇文章中的 CodeFormat.aspx.cs 和 CodeFormat.cs 有标行号,但 Web.config 和 CodeFormat.aspx 就没有标行号。 –  黄志斌 2013-04-20 06:56
这个网页的界面中有个 Line Count 复选框,就是让你选择是否需要标上行号的。 –  黄志斌 2013-04-20 06:58
嗯,我问问我们开发人员 –  傅志红 2013-04-20 09:11

推荐 3
代码:cat /tmp/a | awk '/^ /{a++; sub(/^ /, " " a " ");} !/^ /{a=0;} {print;}'

This is a normal line.

1 a
2 b
3 c
4 d

1 b
2 p
3 m
4 f

This is another normal line.
还是这个最简单,哈哈,用perl再写一段吧 –  白龙 2013-03-25 00:08
awk 的确是 Unix/Linux 下非常强有力的工具。象这段小小的 awk 程序能够给行首有空格的代码加上行号,而且能够在遇到正文文本之后把行号又清零。但是有个小小缺点:行号不是等宽的。 –  黄志斌 2013-03-25 08:08
用printf格式化就可以等宽了 –  Martin Wang 2013-03-25 11:19
等宽的关键是要事先计算出行号的宽度是多少。例如,最大行号是 456 的话,宽度就是 3,最大行号是 5678 的话,宽度就是 4 。 –  黄志斌 2013-03-25 11:30
对于Markdown里的小代码片段,三位数字已足够。 –  梁涛 2013-03-26 08:50
三位数字固然足够,但还有些是一位和两位数字的情况。 –  黄志斌 2013-03-26 09:17
不需要考虑这么多,输出是可以控制左右对齐的。考虑太多,脚本就不实用了。 –  梁涛 2013-03-28 16:52

推荐 3
哈哈,高!实在是高!

推荐 2
我用
··· c

...
这种格式就自动添加了行号。上边.是数字1左边的键

推荐 2
解决这个问题很简单阿,

$ set o=$(expr length $(cat /tmp/t|wc -l)) && echo "awk 'BEGIN{i=0}{printf(\"%${o}d %s\n\", ++i, \$1)}' /tmp/t" > /tmp/render.sh | bash /tmp/render.sh

这样写很丑陋,不过好歹可以满足这个需求;换成脚本就清晰了。简单说下:
1. 统计行数。用wc。
2. 生成一个awk脚本。因为printf %d需要知道偏移。
3. 用Bash执行这个临时awk脚本。(忘了删除它,最好是一个不会冲突的临时文件,用完自动删除之)。
Sorry,以上只解决了等宽问题 Orz –  berlinix 2013-06-01 22:30
唉,想想真是愚蠢阿,cat -n 就能实现上面这行丑陋代码的功能。 cat -n /tmp/t –  berlinix 2013-06-03 08:58

推荐 2
图灵社区不知道能不能搞个网页实现这个功能?这篇文章中已经有全部的源程序了。
这样可以为有这方面需求的作者提供方便。
我没有可用外网公用 IP,没法在外网建立网站。

推荐 2
@梁涛
Unix/Linux 的 awk 的确很好用。这一小段 awk 代码用于给代码加上行号是很不错的,她判断行首有空格者为代码,然后加上连续的等号。碰到行着没有空格的正文,行号又重新归零。如 @梁涛 的例子所示。但是还有个小缺点,行号的不是等宽的,如下所示:
~$ cat /tmp/a | awk '/^ /{a++; sub(/^ /, " " a " ");} !/^ /{a=0;} {print;}'
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
10 j
11 k

在上述程序中,...
在每个行号前插入空格?一般代码3位数行号就足够了,如果能计算总行数后再加行号更好 –  lt 2013-04-23 16:17

我要评论

需要登录后才能发言
登录未成功,请修改提交。