使用 m4 编写 HTML

作者:Bob Hepple

编写简单的 HTML 页面是如此的容易,这真是令人惊讶——并且像 Netscape Gold 这样的 WYSIWYG(所见即所得)HTML 编辑器的出现,让人产生一种“别担心,高兴就好”的心情。然而,管理多个相互关联的 HTML 页面很快就会变得非常困难。我最近需要整理一组稍微复杂的页面,我开始思考,“一定有一种更简单的方法。”

我立即转向万维网,查找各种工具——但老实说,我相当失望。大多数工具都是我所说的“打字辅助工具”——而不是必须记住像 <a href="link"7gt;text</a> text 这样晦涩难懂的咒语,而是给你一个按钮或一个像 alt-ctrl-j 这样的魔法组合键,它可以记住语法并为你完成所有的打字工作。

Linux 来拯救了——由于 HTML 是作为普通的文本文件构建的,因此可以使用普通的 Linux 文本管理工具。这包括版本控制工具,如 rcs 和文本处理工具,如 awkPerl 等。这些工具在版本控制和管理多用户开发方面提供了显著的帮助,并且可以自动化从数据库显示信息的过程(经典的 grep |sort |awk 管道)。

在其他地方已经记录了这些工具在 HTML 中的使用,例如,Jim Weirich 在 1997 年 4 月的《Linux Journal》第 36 期中发表的文章“使用 Perl 检查 Web 链接”。我强烈推荐这篇文章,作为另一种在编写 HTML 时真正锻炼 Linux 肌肉的方法。

我将在这里介绍的是我最近使用预处理器 m4 来维护 HTML 所做的工作。这些想法可以很容易地扩展到更通用的 SGML 案例。

使用 m4

在查看了包括 cpp(C 前端)在内的各种其他预处理器之后,我决定使用 m4,cpp 可能有点太 C 语言特定,以至于不适用于 HTML。m4 是一个通用的、干净的宏扩展程序,并且在包括 Linux 在内的大多数 Unix 系统上都可用。

我没有编辑 *.html 文件,而是使用我最喜欢的文本编辑器创建 *.m4 文件。这些文件看起来像这样

m4_include(stdlib.m4)
_HEADER(`This is my header')
<P>This is some plain text<P>
_HEAD1(`This is a main heading')
<P>This is some more plain text<P>
_TRAILER

格式只是 HTML 代码,但是你可以包含文件并添加宏,就像在 C 语言中一样。我使用一个约定,我的新宏都用大写字母表示,并以 _ 字符开头,使其与 HTML 语言区分开来,并避免命名空间冲突。

然后,使用以下命令处理 m4 文件以创建 .html 文件

m4 -P <file.m4 >file.html

如果您创建一个 makefile 以通常的方式自动化这些步骤,这个过程就特别容易了。例如

.SUFFIXES: .m4 .html
.m4.html:
        m4 -P <$*.m4 >$*.html
DEFault:        index.html
*.html: stdlib.m4
all:    default PROJECT1 PROJECT2
PROJECT1:
        (cd project2; make all)
PROJECT2:
        (cd project2; make all)
这里列出了一些 m4 中最有用的命令,以及它们在 cpp 中的等效命令(在括号中显示)
  • m4_include: 将公共文件包含到您的 HTML 中 (#include)

  • m4_define: 定义一个 m4 变量 (#define)

  • m4_ifdef: 一个条件语句 (#ifdef)

  • m4_changecom: 更改 m4 注释字符(通常为 #)

  • m4_debugmode: 控制错误诊断

  • m4_traceon/off: 打开和关闭跟踪

  • m4_dnl: 注释

  • m4_incr, m4_decr: 简单的算术运算

  • m4_eval: 更通用的算术运算

  • m4_esyscmd: 执行 Linux 命令并使用输出

  • m4_divert(i): 这有点复杂,所以第一次阅读可以跳过。这是一种存储文本以便在正常处理结束时输出的方法。当我们进行标题的自动编号时,它将在稍后派上用场。它将 m4 的输出发送到临时文件编号 i。在处理结束时,任何被转移的文本都将按照文件编号 i 的顺序输出。文件编号 -1 是位桶,可以用来注释掉大段的注释。文件编号 0 是正常的输出流。因此,例如,您可以使用 m4_divert 将文本转移到文件 1,它将仅在最后输出。

在多个页面之间共享 HTML 元素

在许多 HTML 页面的“巢穴”中,每个页面都共享元素,例如包含指向其他页面的链接的按钮栏,如下所示

[Home]  [Next]  [Prev]  [Index]

这在每个页面中都相当容易创建。问题是,如果您更改“标准”按钮栏,那么您需要进行繁琐的工作,在每个文件中找到它的每个实例并手动进行更改。使用 m4,我们可以通过将共享元素放入 m4_include 语句中来更轻松地完成这项工作,就像 C 语言一样。

让我们还通过将以下行放入名为 button_bar.m4 的包含文件中来自动化页面的命名

m4_define(`_BUTTON_BAR',
 <a href="homepage.html">[Home]</a>
 <a href="$1">[Next]</a>
 <a href="$2">[Prev]</a>
 <a href="indexpage.html">[Index]</a>)

然后在文档中添加以下行

m4_include button_bar.m4
_BUTTON_BAR(`page_after_this.html',
 `page_before_this.html')
宏定义中的 $1 和 $2 参数被宏调用中的字符串替换。
管理经常更改的 HTML 元素

在多个 HTML 页面中更改项目是很麻烦的。例如,如果您的电子邮件地址更改了,您需要将所有对它的引用都更改为新地址。相反,使用 m4,您可以在您的 stdlib.m4 文件中添加如下行

m4_define(`_EMAIL_ADDRESS', `MyName@foo.bar.com')

然后只需在您的 m4 文件中放入 _EMAIL_ADDRESS 即可。

一个更实质性的例子来自于构建具有多个组件的字符串,其中任何一个组件都可能随着页面的开发而更改。如果像我一样,您在一台机器上开发,测试页面,然后上传到另一台地址完全不同的机器上,那么您可以使用 stdlib.m4 文件中的 m4_ifdef 命令(就像 cpp 中的 #ifdef 命令一样)。例如

m4_define(`_LOCAL')
...
m4_define(`_HOMEPAGE',
 m4_ifdef(`_LOCAL',
 `//127.0.0.1/~YourAccount',
 `http://ISP.com/~YourAccount'))
m4_define(`_PLUG', `<A HREF="http://www.ssc.com/linux/">
<IMG SRC="_HOMEPAGE/gif/powered.gif"
ALT=<"[Linux Information]"> </A>')

请注意小心使用引号以防止变量 _LOCAL 被扩展。_HOMEPAGE 根据变量 _LOCAL 是否定义而采用不同的值。然后,当您构建页面时,此定义可以贯穿整个项目。

在这个例子中,_PLUG 是一个宣传 Linux 的宏。当您测试页面时,请使用 _HOMEPAGE 的本地版本。当您准备好上传时,请以这种方式删除或注释掉 _LOCAL 定义

m4_dnl m4_define(`_LOCAL')

... 然后重新 make。

创建新的文本样式

内置于 HTML 中的样式包括诸如 <EM> 用于强调和 <CITE> 用于引用的样式。使用 m4,您可以定义自己的新样式,如下所示

m4_define(`_MYQUOTE',
 <BLOCKQUOTE><EM>$1</EM></BLOCKQUOTE>)

如果稍后您决定更喜欢 <STRONG> 而不是 <EM>,那么更改定义就很简单了。然后,每个 _MYQUOTE 段落都会通过快速的 make 命令与新的定义保持一致。

关于良好 HTML 写作的经典指南说:“强烈建议您在文档中使用逻辑样式,例如 <EM>...</EM>,而不是物理样式,例如 <I>...</I>。” 奇怪的是,HTML 的 WYSIWYG 编辑器生成纯粹的物理样式。使用 m4 样式可能是继续使用逻辑样式的好方法。

打字和助记符辅助

我不依赖 WYSIWYG 编辑(因为我是使用 troff 长大的),但尽管如此,我并不反对在可用时使用帮助。在以下两者之间做出选择(也许这只是一线之隔)

<BLOCKQUOTE><PRE><CODE>Some code you want to display.
</CODE></PRE></BLOCKQUOTE>

_CODE(Some code you want to display.)
在这种情况下,您将像这样定义 _CODE
m4_define(`_CODE',
<BLOCKQUOTE><PRE><CODE>$1</CODE></PRE></BLOCKQUOTE>)
您喜欢哪个版本取决于个人品味和便利性,尽管 m4 宏肯定可以节省一些打字时间。我喜欢使用的另一个例子是,因为我总是记不住链接的语法,所以是
m4_define(`_LINK', <a href="$1">$2</a>)
然后,我不输入
<a href="URL_TO_SOMEWHERE">Click here to get to SOMEWHERE
</a>
我输入
_LINK(`URL_TO_SOMEWHERE', `Click here to get to SOMEWHERE')
自动编号

m4 有一个简单的算术工具,带有两个运算符 m4_incrm4_decr。此工具可用于创建自动编号,例如用于标题,例如

m4_define(_CARDINAL,0)
m4_define(_H, `m4_define(`_CARDINAL',
 m4_incr(_CARDINAL))<H2>_CARDINAL.0 $1</H2>')
_H(First Heading)
_H(Second Heading)

这会产生

<H2>1.0 First Heading</H2>
<H2>2.0 Second Heading</H2>
自动日期戳记

对于 HTML 页面的简单日期戳记,我使用 m4_esyscmd 命令来维护每个页面上的自动时间戳

This page was updated on m4_esyscmd(date)

这将产生

This page was last updated on Fri May 9 10:35:03 HKT 1997
生成目录

使用 m4 允许您定义常用的重复短语并一致地使用它们。我讨厌重复自己,因为我很懒,而且因为我会犯错误,所以我发现这个功能绝对是必要的。

m4 强大功能的一个很好的例子是在大型页面中构建目录。这涉及到在目录和文本本身中重复标题标题。这是乏味且容易出错的,尤其是在您更改标题时。有一些专门的工具可以从 HTML 页面生成目录,但是 m4 提供的简单功能对我来说是不可抗拒的。

简单易懂的目录

以下示例是一个相当简单的目录生成器。首先,在 stdlib.m4 中创建一些有用的宏

m4_define(`_LINK_TO_LABEL',
 <A HREF="#$1">$1</A>)
m4_define(`_SECTION_HEADER',
 <A NAME="$1"><H2>$1</H2></A>)

然后在页面正文的开头定义所有节标题的表格

m4_define(`_DIFFICULTIES',
 `The difficulties of HTML')
m4_define(`_USING_M4', `Using
 <EM>m4</EM>')
m4_define(`_SHARING', `Sharing HTML
 Elements Across Several Pages')
然后构建表格
<UL><P>
 <LI> _LINK_TO_LABEL(_DIFFICULTIES)
 <LI> _LINK_TO_LABEL(_USING_M4)
 <LI> _LINK_TO_LABEL(_SHARING)
<UL>
最后,编写文本
 ...
_SECTION_HEADER(_DIFFICULTIES)
...
这种方法的优点是双重的。如果您更改标题,您只需在一个地方更改它们,然后目录就会自动重新生成。此外,链接保证可以工作。
简单易用的目录

我通常使用的目录生成器稍微复杂一些,需要更多的研究,但它更容易使用。它不仅构建目录,而且还自动动态编号标题——最多四级编号(例如,第 3.2.1.3 节),尽管这可以很容易地扩展。它使用起来非常简单,如下所示

  1. 在您想要显示目录的位置,调用 Start_TOC

  2. 在每个标题处,根据需要使用 _H1(`1 级标题')_H2(`2 级标题')

  3. 在 HTML 代码的最后一行(可能是 </HTML>)之后,调用 End_TOC

这些宏的代码显示在列表 1中。一个限制是您不应在文本中使用转移(即 m4-divert),除非您保留此 TOC 生成器使用的文件 1 的转移。

简单表格

除了目录之外,许多浏览器都支持表格信息。以下是一些有趣的宏,作为生成这些表格的快捷方式。首先,一个使用示例(见图 1)

<CENTER>
_Start_Table(BORDER=5)
_Table_Hdr(,Apples, Oranges, Lemons)
_Table_Row(England, 100,250,300)
_Table_Row(France,200,500,100)
_Table_Row(Germany,500,50,90)
_Table_Row(Spain,,23,2444)
_Table_Row(Danmark,,,20)
_End_Table
</CENTER>
Writing HTML with m4

图 1. 表格示例

m4 陷阱

不幸的是,m4 需要一些驯服。花一点时间熟悉它将带来回报。有权威的文档可用(例如,在 Emacs info 文档系统中),但是,在不成为完整教程的情况下,这里有一些基于我的经验的提示。

陷阱 1——引号

m4 的引号字符是重音符 `,它开始引号,而锐音符 ',它结束引号。将宏的所有参数放在引号中可能会有所帮助,例如

_HEAD1(`This is a heading')

使用引号的主要原因是防止当逗号包含在宏的参数中时造成混淆,因为 m4 使用逗号来分隔宏参数。例如,行 _CODE(foo, bar) 会将 foo 放入 HTML 输出中,但不会放入 bar。在行 _CODE(`foo, bar') 中使用引号,它就可以正常工作。

陷阱 2——单词吞噬

m4 的最大问题是一些版本会 吞噬 它识别的关键字,例如 includeformatdivertfilegnulineregexpshiftunixbuiltindefine。您可以通过将这些单词放在单引号中来保护它们,例如

Smart people `include' Linux in their list
of computer essentials.

问题是,这既不方便又容易忘记。

保护关键字的更安全的方法(我的偏好)是使用 -P--prefix-builtins 选项调用 m4。然后,所有内置宏名称都会被修改,以便它们都以 m4_ 前缀开头,而普通单词保持原样。例如,使用此选项,人们将编写 m4_define 而不是 define(如本文示例所示)。一个障碍是并非所有版本的 m4 都支持此选项——最值得注意的是 MS-DOS 下的一些 PC 版本。

陷阱 3——注释

m4 中的注释行以 # 字符开头——从 # 到行尾的所有内容都将被忽略并保持不变输出。如果您想在 HTML 页面中使用 #,您必须像这样引用它:`#'。另一个选项(我的偏好)是将 m4 注释字符更改为一些特殊的字符,如下所示

m4_changecom(`[[[[')

而不必担心文本中的 # 符号。

如果您想在 m4 文件中使用注释,但不希望它们出现在最终的 HTML 文件中,请使用 宏 m4_dnl (dnl = Delete to New Line)。此宏会抑制所有内容,直到下一个换行符。

m4_define(_NEWMACRO, `foo bar')
m4_dnl This is a comment

忽略源代码的另一种方法是 m4_divert 命令。m4_divert 的主要目的是将文本保存在临时缓冲区中,以便稍后包含在文件中——例如,在构建目录或索引时。但是,如果您转移到“-1”,它就会进入虚无之地。此选项对于消除 m4_define 命令生成的空白非常有用。例如

m4_divert(-1) diversion on
m4_define(this ...)
m4_define(that ...)
m4_divert diversion turned off
陷阱 4——调试

当事情出错时,另一个提示是增加 m4 输出的错误诊断的数量。最简单的方法是将以下内容添加到您的 m4 文件中作为调试命令

m4_debugmode(e)
m4_traceon
...
buggy lines
...
m4_traceoff
结论

应该注意的是,HTML 3.0 确实有一个 include 语句,如下所示

<!--#include file="junk.html" -->

但是,HTML include 具有以下限制

  • 包含和解释 include 的工作是在服务器端完成的,然后在下载之前完成,并且增加了开销,因为服务器必须扫描文件以查找 include 语句。

  • 大多数服务器(尤其是公共 ISP)都禁用了此功能,因为开销很大。

  • Include 就是您所能得到的全部——没有宏替换,没有宏参数,没有 ifdef 等,就像 m4 一样。

m4 还有一些其他功能,到目前为止,我还没有在我的 HTML 漫谈中利用它们,例如正则表达式。创建一个“标准”stdlib.m4 以供通用使用,其中包含用于通用文本处理和 HTML 功能的不错的宏,这可能会很有趣。请务必下载我的 stdlib.m4 版本,作为您自己 hack 的基础。如果有足够的兴趣,我很乐意听到有用的宏,也许可以从本文中发展出一个 Mini-HOWTO。

使用 Linux 开发 HTML 页面还有许多额外的优势,远远超出了典型的打字辅助工具和 WYSIWYG 工具提供的简单帮助。当然,我将继续使用 m4,直到 HTML 赶上——然后我将进行最后一次 make 并退回到使用纯 HTML。我希望您喜欢这些小技巧,并鼓励您贡献自己的技巧。

Writing HTML with m4
Bob Hepple 自 1981 年以来一直在各种借口下 hack Unix,并且不知何故至少在一段时间内为此获得了报酬。这让他能够追求另一个爱好——居住在温暖、充满异国情调的国家,包括香港、澳大利亚、卡塔尔、沙特阿拉伯、莱索托和(目前)新加坡。他对寒冷的最初厌恶是在英国学到的。雄心壮志——停止为信用卡公司和税务员工作,并找到一份真正的工作。可以通过 bhepple@pacific.net.sg 联系到 Bob。
加载 Disqus 评论