作为一种标记语言,HTML 在互联网中的广泛采用佐证了它在发布内容方面的实用性是无与伦比的。HTML 向结构内容提供各种标记,其中包括处于不同级的标题标记,例如从 <h1> 到 <h6>,或定义列表的标记,例如 <ul> 或 <ol>。HTML 文档是简单的文本文档,可以使用任何编辑器加以创建或从当前办公产品套件中导出的文档进行创建。HTML 缺乏对一些项目(例如章节、图、表格)交叉引用的真正支持。要是这些项目不被移动到不同的位置、被添加内容或完全被移除,对于此类项目维持有序的编码制加上确保对这些项目从文档中的任意其他位置引用保持最新超出了 HTML 的范围。然而,此类功能很容易获得,特别当在多文档间管理此类引用时尤为如此。本文将介绍一款名为 Xref 的 Java 工具,它提供此类功能以及其他一些功能。该工具演示了新的开发简易性功能(已经添加到即将发行的 J2SE 5.0 发行版“Tiger”中)的好处。本文所使用的主要功能有通用性、类型安全枚举和其他语言扩展,像为收集而提供的增强 for 循环。
我通常使用 HTML 文档来管理信息。这种页面描述语言的使用非常简单,将有用数据存储在 web 页面中非常方便 —— 在堆之间没有闲置的文档和文档丢失,这些情况再也不会出现了。假定更为复杂的 HTML 文档是有序的(<ol>)或无序的(<ul>),通常使用像从 <h1> 到 <h6> 的标题标记(尽管后面的标题标记不经常使用)或列表加以构造。此外,在文档中其他结构性元素之间还可能存在图或表格。通常像章节标题、表格和图这样的元素得到分配给能够引用它们的编号,例如:
......
3. 配置代理服务器
......
3.1 设置服务器
......
3.2 配置代理规则
......
在文本中的某些位置上我们可以找到类似
......
有关设置代理服务器的更多信息,请参见第 3.1 章。
......
使用图和表格的工作原理相同。此时,问题变得明朗了: 如果在现有结构之间插入某些内容,例如一个新的章节 3.1,所有后面的章节标题必须重新编号。除此之外,对章节编号的引用也必须修改。这显然既烦人 —— 更糟糕的是 —— 又容易发生错误,特别是如果此类编号和引用跨越多个构成实际文档的 HTML 文件,这种问题尤为突出。
在您办公套件中的日常文字处理程序提供了另一个宜人的功能: 自动创建目录或图列表和表格列表。在 HTML 中,此类列表必须手动创建和维护。这还不是我们通常乐于做的事情。
本文中描述的 Xref 工具对这些问题提供了一个简单的解决方案以及一些附加功能。除此之外,该工具还是如何高效利用一些 J2SE 5.0 新功能的有用示例。
当然 Xref 本身可以以多种方式简化 HTML 内容的创建,其主要目标是 web 创作区域,其中创建了跨越一个或多个 HTML 文档的文本。该区域在几种复杂性级别上受到支持。
在其应用程序的最简单形式中,使用 HTML 标题标记进行构造的现有文档可以被处理,且标题将被自动编号。这一结果在不进行任何更改的情况下获得。如果需要,带有指向所有章节的所有章节和超链接目录可以通过向文档简单添加一个新标记而生成。这是惟一的必要更改。
在 HTML 文档中,应用程序的更复杂形式要求多项更改: 通过添加新的 HTML 标记,对于其他像图或表格这样的对象可以获得另外的自动编号。再者,还支持列表(图列表或表格列表)的创建
完整的 Xref 功能通过使用引用功能实施。使用另一个新标记 (<ref>)对任何被添加标签对象(例如章节标题、图或表格)的引用可以在文档内创建,且将正确的编号插入在相同位置。除此之外,指向被引用的对象的超链接也将被创建。
所有在本文中描述的源代码和带有编译后的类的 JAR 文件可下载 —— 请参见 参考资料 部分。
尽管许多流行的浏览器还不支持本文中描述的一些功能,但它们还可以在 CSS2 中所指定的计数器基础上实现 (请参见 参考资料 部分)。这些计数器还可以被用于自动编号,例如章节标题的自动编号。在本文描述的工具更加专注于交叉引用任务,并提供除 CSS2 计数器之外的几项功能:
像图和表格(以及其他任意序列)这类项目的编号基于新的伪 HTML 标记,直接插入到文档内。
包括对被编号项目(像章节或图)的引用解析、针对目标的指定锚点的创建和 <a href> 这样的引用在内的自动交叉引用功能。
在文档中,包括对所引用项目的 <a href> 引用在内的交叉引用列表的自动生成。
跨文档功能(编号和引用能跨越多个文档 )。
除此之外,针对 J2SE 5.0 发行版的一些新功能还旨在成为展示应用程序。
针对交叉引用进程的标记
已经对三个新标记加以定义以便达到该工具的目的:
标记 <lab name="myLabel" type="figure">...</lab>:
该标记在随后即被引用的 HTML 文档中设置给定类型的标签。该标签还接受可选的 text 属性,用该属性可以显式设置用于在自动生成列表(默认时,在开始标记和结束 <lab> 标记之间包含的文本被用于这一目的 )中的标签的文本。关于这方面的更多详细信息稍后加以介绍。
标记 <ref name="MainData" type="table">...</ref>:
该标记通过其名称引用给定类型的标签。
标记 <list type="chapter"></list>:
该标记将已知标签的自动生成列表插入到文档中,例如目录或图列表。
除此之外,现有标题标记从 <h1> 到 <h6> 已经被扩展以便接受均为可选的附加 name 和 text 属性。这一点非常有用,因为这些标题标记对于 HTML 已经是现有的构造机制,而不是针对每个章节标题进行定义的额外 <lab> 标记,依赖于现有标记更有用。因此基本上不用
<lab name="Chapter1" type="chapter">The first chapter</lab>
而是使用
<h1 name="Chapter1">The first chapter</h1>
这样提供了一个极其重要的额外好处: 如果使用 HTML 标题标记构造目录的现有文档需要使用该工具进行处理,则不需要对文档做实际修改来获得章节标题的自动编号! 如果没有出现此类属性,XrefParser 对标题标记的 name 属性使用伪值。标题的编号惟一建立在文档结构基础上,而不是建立在标题标记的属性,因此,此类未修改的文档要使用正确的标题编号加以重写。当然,不可能添加指向此类标题的 <ref> 引用。然而此类附加信息根据需要可以被插入到现有文档中 —— 因此绝对不必为了获得该工具的某些好处而重写 HTML 文档。但是,要获得该工具的全部优点需要一定的重写.
在此给出几项注解:
使用任何标准的文本编辑器可以将这些标记插入到文档中。HTML 编辑器还支持 HTML 标记的插入(Netscape 编写器是提供该项功能的编辑器的一个示例 )。
在此采用的标记名称无可否认部分借用了 Leslie Lamport 的 LaTeX 宏软件包,该软件包建立在 Donald Knuth 极好的文本编辑器 TeX 之上。这可能是 sun 用于创建技术和科学文档的 最好 工具,特别是如果这些标记名称包含许多公式和等式的情况下尤为如此。
我们当然可以定义任何 HTML 标记的编号并将它们插入文档中 —— 这并不意味着没有这些标记浏览器就会变得重要了。幸运的是,浏览器不太关心这些标记而只是忽略它们。这样实际上很好,因为这意味着这些标记不在源文档中显示。在由该工具创建的目标文档中,已经将适当的文本(标题、表格或图编号 )添加到标记所指定的位置。
我们此处使用的 org.htmlparser.Parser 不 忽略这些标记当然是个好消息。事实上,它为每个标记提供了 org.htmlparser.lexer.nodes.TagNode 的一个实例,且 getRawTagName() 方法提供了标记名称作为可用于进一步处理的字符串。
XrefParser 不仅使用正确的编号解析所有的引用,而且还针对引用目标插入 HTML 锚点 ( <a name> ... </a> )和对引用提供超链接,因此导航得到充分的简化。还在由 <list> 标记所创建的列表内为每个项目创建超链接
对于所引入的 <ref> 和 <list> 标记,结束标记并不是在所有情况中都是必须的。但是,它是使用结束标记兼容 xHTML 是一个很好样式. 对于 <lab>标记,必须使这些结束标记顾及文本数据的自动收集。
技术实现
此处所选择的方法是定义被添加到 HTML 文档中的一些新 HTML 标记,并扩展带有附加属性的标准标题标记 <h1> 到 <h6> 。此类 HTML 文档使用 HTMLParser Sourceforge 项目的一部分 org.htmlparser.Parser 的实例加以处理。(参见 参考资料 部分 )。这个类提供一些功能来解析 HTML 文档并以树型结构的形式提供文档信息。该树型结构基本上由三个不同的节点元素组成:
org.htmlparser.lexer.nodes.TagNode: 一个含有关于解析器所遇到 HTML 标记的所有信息的节点。
org.htmlparser.lexer.nodes.TagNode: 一个含有纯 HTML 文本的节点。
org.htmlparser.lexer.nodes.TagNode: 一个含有关于解析器所遇到 HTML 注释的所有信息的节点。
该树型结构可以被递归处理,并根据所遇到节点的类型采取适当的操作。很明显,为达到 Xref 工具的目的,由于我们正寻找特定的标记来触发一定操作,TagNode 是最相关的节点,但是 StringNode 对于向文档添加附加信息也非常重要。
org.htmlparser.Parser 类 —— 或者更精确地讲,整个 HTMLParser 包 —— 的确是一个好软件,我为制作该软件的作者拍手喝彩! 它是一个健壮的解析器,可以不仅完美处理顺应 xHTML 的文档,而且还正确解析带有大量不那么符合样式指南的 HTML 文档,不过,对现今的浏览器来说它还是可接受的。可能此类 HTML 标记的最常见示例总是丢失像 </td> 或 </p> 这样的结束标记。不过,org.htmlparser.Parser 正确解析此类文档并生成文档树型结构。该结构可以被处理,使用类似下列示例代码:
//.... Parse a file whose name is stored in the String fileName
org.htmlparser.Parser parser = new org.htmlparser.Parser(fileName);
for (org.htmlparser.util.NodeIterator i = parser.elements();
i.hasMoreNodes(); ) {
org.htmlparser.Node node = i.nextNode();
nodes.add(node);
recurse(node);
}
其中,通过树的递归路径被实现,如下所示:
private void recurse(org.htmlparser.Node node) throws
org.htmlparser.util.ParserException {
//.... Do processing for the given node (depending on the node type)
...
//.... Recurse into children
org.htmlparser.util.NodeList nodeList = node.getChildren();
if (nodeList != null)
for (org.htmlparser.util.NodeIterator i = nodeList.elements(); i.hasMoreNodes(); )
recurse(i.nextNode());
}
org.htmlparser.Parser 类的一个非常方便的功能是不局限于已知的 HTML 标记集合,而是使用更多的模式匹配方式识别标记。这使得我们暗暗将新引入的标记插入文档,随后这些标记像标准的 HTML 标记那样作为 TagNode 实例加以报告。
一旦文档的树型结构可用,它就会被处理两次: 在第 1 次处理中,收集关于可引用对象的所有必要信息,第 2 次处理,解析在文档中所有对此类对象的引用,并执行对此类对象的自动编号。所有这些过程在 Xref 工具 至关紧要的 组件 ml.htmlkit.XrefParser 类中被实现。
因此,对这一过程做如下总结:
创建 XrefParser 的实例:
XrefParser parser = new XrefParser();
针对 inpfile 文件运行第 1 次处理:
parser.collect(inpfile);
这一过程创建深入实质的 org.htmlparser.Parser 以便创建文档树,然后正确使用上面所述的递归过程遍历该树并检查所有元素。
针对 inpfile 文件运行第 2 次处理:
parser.resolve(inpfile, outfile);
这一步再次递归遍历文档树(被存储在第 1 次处理中的 XrefParser 实例中 ) 以便解析所有引用,并在需要插入编号的地方插入编号。输出内容被写入给定的文件 outfile 中。
无可否认这是稍作简化的视图,但可以演示这一基本方法。
Xref 以容易使用的方式实现该过程。该应用程序接受以项目的形式描述要完成任务的 XML 文件,以及在文章的后边加以描述的详细内容。此时,指出以下几个方面很重要:
XrefParser 和 Xref 在项目内支持 多个 HTML 文档的处理。引用可以跨越多个文档,且所生成并被插入到文本中的链接将正确解析适当的文档。
XrefParser 接受一组参数以便控制生成输出的某些方面。对于这些默认选择的参数可以在描述交叉引用项目的 XML 文件内覆盖。
Xref 还可以被实例化且实际的交叉引用任务还可以通过调用
public void xrefFiles(java.util.List<String> inpfiles,
java.util.List<String> outfiles,
XrefParser parser) throws java.io.IOException,
org.htmlparser.util.ParserException
方法获得。这样允许从其他 Java 应用程序内使用该工具的这些功能。
还应当指出的是,该工具的初始版本依赖于在 Swing 包(是标准 JDK (javax.swing.text.html.HTMLEditorKit)的一部分)中所提供的 HTML 编辑器工具箱。该工具箱还包含一个可以容易应用于当前任务的 HTML 解析器。然而,不幸的是,解析器当前使用深入实质的 HTML 3.2 DTD (还会出现在即将发行的 J2SE 5.0 中 ),这意味着 HTML 4.0 (或更新版本 ) 功能可能引起这些问题。这些被观察的此类问题的示例之一是在 HTML 4.0 中被引入的实体(像 α )的不正确处理。由于一些最新引入的实体被认为对于 Xref 工具有用,最终,Swing 解析器会被没有这些局限性的 HTMLParser 包所替代。