XMLC

作者:Reuven M. Lerner

在过去的几个月中,我们研究了使用服务器端 Java 创建 Web 应用程序的各种方法。我们从简单的 servlet 开始,然后转向 JavaServer Pages (JSP)。为了从 JSP 中移除 Java 代码,我们开始使用 JavaBeans,这些对象的方法可以自动用于我们的页面。

但是 JavaBeans 的作用是有限的,这就是自定义操作的用武之地。这些操作在我们的 JSP 中看起来像 XML 标签和属性,它们与 Java 类的方法绑定。换句话说,在我们的 JSP 中放置一个标签可以有效地调用一个或多个方法。将自定义标签与 bean 结合使用,我们可以从 JSP 中移除相当多的 Java 代码。

但最终,我们完成了什么?正如我们上个月所看到的,智能地使用自定义操作意味着创建我们自己的微型语言,它有自己的循环、条件语句和变量。编写我们自己的标签可以使平面设计师不必使用 Java,并使我们能够更好地分离形式和内容。但这在解决问题方面还远远不够。

一个巧妙的解决方案是 Enhydra 应用服务器的一部分,在接下来的几个月中我将对此进行撰写。XMLC,或 XML 编译器,将 XML 文件(包括 HTML 和 XHTML 文件)转换为 Java 对象。通过调用这些对象的方法,我们可以修改最终生成的 HTML。

XML 和 XHTML

XML,正如您现在可能已经听说的,是可扩展标记语言。几年前,它还是一个简单而小的标准,现在已经膨胀成名副其实的标准和提议标准的大杂烩。

但是 XML 的核心仍然保持不变,允许人们使用统一的语法创建自己的标记语言。XML 并非旨在直接使用;相反,它是为了让您创建自己的标记语言。由于这些标记语言基于 XML,因此它们具有任何 XML 解析器都可以验证的、易于理解的语法。此外,如果您为您的标记语言定义了数据类型定义 (DTD),则验证解析器可以确保元素和属性在可接受的规范范围内。

HTML 和 XML 都是万维网联盟 (W3C) 的标准,具有相似的语法,并且经常被相提并论。但实际上,HTML 只是一种标记语言,而 XML 允许您创建自己的语言。更重要的是,由于历史因素,HTML 的语法比 XML 松散得多。因此,以下是合法的 HTML:<img src="foo.png">

但是,由于在 XML 派生的语言中,每个标签都必须显式关闭,因此这在 XML 文档中是非法的。相反,我们必须说:<img src="foo.png"/>

为了弥合 HTML 和 XML 之间的差距,W3C 发布了一项名为 XHTML 的建议,它是 HTML 的 XML 实现。虽然使用 XHTML 确实有各种好处,但最大的好处是 XML 工具现在可以在我们的 HTML 文档上工作。

当然,这意味着我们的 XHTML 文档看起来会比我们可能习惯编写的 HTML 文档更正式一些。虽然 HTML 允许我们马虎,使用 <P> 分隔段落,但 XHTML 更加严格,迫使我们以 <P> 开始段落并以 </P> 结束段落。属性也必须出现在双引号中,许多人在使用纯 HTML 时未能做到这一点。

虽然 XHTML 对人类来说可能很麻烦,但它实际上通过使语法更规范来减少程序的负载,从而更易于读写。但最大的好处是 XHTML 文档现在可以被视为 XML 文档。

DOM

XML 文档是树状结构,对于那些在大学学习计算机科学的人来说,这应该会敲响警钟。从理论上讲,树非常容易使用,但实践有时会有点棘手,这取决于接口的实现方式。

有两种流行的跨平台 API 用于处理 XML:SAX(XML 简单 API)旨在处理传入的 XML 数据流,使其体积小且效率高。相比之下,DOM(文档对象模型)使我们能够一次访问整个文档树。这允许我们遍历和修改节点,包括添加新节点和删除旧节点。但是,这也意味着必须将整个文档加载到内存中,然后我们才能开始使用 DOM 处理文档。这使其比 SAX 更强大,但也更慢且资源消耗更大。

XMLC 的工作原理是将 XML 文件(通常以 HTML 或 XHTML 编写)转换为 Java 类,该类创建和操作 DOM 树。您可以使用标准的 DOM 方法在树上添加、修改和删除节点,从而更改最终输出的文档。

但 XMLC 中真正巧妙的想法是使用 HTML “id” 属性。当 XMLC 编译器看到 id 属性时,它会创建允许我们检索和修改该属性中包含的文本的方法。因此,站点设计人员使用 HTML,通过为动态文本区域指定唯一的标识符来识别它们。当设计人员完成原始 HTML 页面的模型后,他们会使用 XMLC 将其编译为 Java 类。然后,开发人员创建 servlet 来实例化该类,使用方法将模型文本替换为动态生成的内容,并将文档发送到用户的浏览器。

基本思想是设计人员不处理文本和 HTML 的混合体,而是处理最终输出的模型。只要 id 属性不更改,HTML 文件和 servlet 就可以并行演进,设计人员和开发人员都不必等待对方。

安装 Enhydra

正如我上面提到的,XMLC 是 Enhydra 应用服务器的一个组成部分。3.x 版本的 Enhydra 被认为是生产就绪的,并包含 XMLC 的副本,大多数用户会发现它完全足够。因为我对 Enhydra 用于 Enterprise JavaBeans (EJB) 特别感兴趣,所以我一直在使用 4.x 的 beta 版本,也称为 Enhydra Enterprise。在您阅读本文时,Enhydra Enterprise 的最终版本应该已经可用,为 Web 开发人员提供了一个开源的、生产质量的 J2EE 兼容的应用服务器。

为了使用 XMLC,我下载了 Enhydra Enterprise beta,一个名为 enhydra4.0.tar.gz 的 15.7MB 文件。打开此文件,您将找到 Enhydra 应用服务器的大量库、应用程序和文档。我们现在将忽略其中的大部分内容,暂时专注于 XMLC。

几乎所有的 Enhydra 都是作为从 shell 脚本调用的 Java 类编写的。为了使 shell 脚本找到 Java 类,必须为您的特定安装配置它们。您可以通过进入 Enhydra 目录(在我的系统上是 enhydra4.0)并运行 configure 脚本来执行此操作

./configure /usr/java/jdk1.3

configure 通常接受一个参数,即您的 JDK 1.3 安装的根目录。虽然早期版本的 Enhydra(特别是早期版本的 Enhydra Enterprise)无法与 JDK 1.3 一起使用,但当前版本只能与 1.3 一起使用。由于 JDK 1.3 具有许多其他优点,并且 Sun 支持 Linux 版本,因此安装它可能是一个好主意。

如果您已将 Enhydra 安装在 /usr/local/enhydra 以外的其他位置,则可能应该将 ENHYDRA 环境变量设置为您的安装目录。

充分利用 XMLC 取决于将三个不同的 .jar 文件放在您的 CLASSPATH 中。由于在本文的其余部分中我们将专注于 XMLC,我们现在应该添加它们,使用 bash 语法

export CLASSPATH=$ENHYDRA/lib/xmlc.jar:\
$ENHYDRA/lib/enhydra.jar:\
$ENHYDRA/lib/xmlc-support.jar

如果您像我一样,除了 Enhydra 相关项之外,您还会希望在 CLASSPATH 中包含许多项。例如,以下是我设置 CLASSPATH 的方式

export CLASSPATH=$ENHYDRA/lib/xmlc.jar:\
$ENHYDRA/lib/enhydra.jar:\
$ENHYDRA/lib/xmlc-support.jar:\
$TOMCAT_HOME/classes:\
$TOMCAT_HOME/lib/servlet.jar:\
/usr/share/pgsql/jdbc7.1-1.2.jar:\
.
请注意我是如何将 Enhydra .jar 文件放在我系统上的其他文件之前,以避免潜在的冲突问题。由于 Enhydra 拥有某些类的最新版本,例如与 DOM 相关的类,因此它们应该优先。

请注意,并非所有三个 Enhydra 提供的 .jar 文件对于 XMLC 的每个工作阶段都是必需的。但是,我发现为了避免以后出现不愉快的意外,在所有阶段都包含它们很方便。

一个简单的 HTML 文件

既然我们已经安装了使用 XMLC 所需的一切,让我们用一个简单的 HTML 文件来尝试一下

<html>
    <head><title>This is a title</title></head>
    <body>
        <h1>This is a headline.</h1>
        <p id="firstpara">This is a paragraph.</p>
        <img src="foo.gif"/>
        <p>This is a second paragraph.</p>
    </body>
</html>

虽然 XMLC 可以很好地处理纯 HTML 文件,但 XHTML 是一个更好的主意,因为它阻止我们生成 DOM 无法表示的文件。例如,XML 禁止重叠标签

<i><p>Wow</i>, he thought.</p>
上面是可以容忍的 HTML,但在 XML 和 XHTML 中是非法的。因此,虽然您的 Web 浏览器可以以某种方式处理此 HTML 并理解它,但 XMLC 将生成警告,指示它正在丢弃它认为无用的闭合标签。当您的 HTML 格式不正确时,XMLC 通常会警告您,帮助您识别潜在的问题。虽然在编写简单的 HTML 文档时您可能不必考虑文档的结构,但您可以使用 XMLC 执行的操作要求您清楚地了解文档的呈现方式。

前面示例语句中的第一个段落使用 id 属性 “firstpara” 标识。我们很快就会看到如何从 Java 程序中操作该文本,使用 id 作为进入文档的杠杆。

为了将我们的文档转换为 Java 类,我们调用 xmlc 程序。假设我们上面的 HTML 文件名为 foo.html,我们可以说

$ENHYDRA/bin/xmlc -parseinfo -verbose -keep foo.html

这会将 foo.html 转换为名为 foo.java 的 Java 源文件,然后将其编译为 foo.class。-keep 参数保留 foo.java,而不是在将其编译为 foo.class 后删除它。虽然它们是不必要的,但我在使用 xmlc 时喜欢使用 -parseinfo 和 -verbose,仅仅是为了获得关于编译过程的一些视觉反馈。

XMLC 创建的 Java 源代码相当长且枯燥,尽管注释良好。对于我们这些想要修改 foo.html 的人来说,foo.java 最重要的部分是 getElementFirstpara() 和 setTextFirstpara() 方法。前者返回与 id “firstpara” 关联的文本,而后者允许我们将该文本与任意字符串交换。

列表 1 包含一个小型命令行 Java 类 (PrintFoo.java) 的源代码,该类打印 foo.html 的 Java 化版本的 内容。在打印之前,它使用 setTextFirstpara() 来修改输出

myfoo.setTextFirstpara("This has been changed");

一旦我们进行了更改,我们就可以显示文档

System.out.print(myfoo.toDocument());
我们可以自己遍历 DOM 树,查找具有特定 id 的节点,然后手动修改它。但是,XMLC 的便捷方法使修改此类文本变得非常容易和直接。

列表 1. PrintFoo.java

如果您刚刚运行了 PrintFoo,您会注意到输出的 HTML 在显示时没有任何原始的空格。生成的文档更难让人阅读,但在浏览器中的呈现方式相同。也就是说,我一直试图保持我的 HTML 文档格式正确,以便于调试,如果 XMLC 包含一个 -preserve-whitespace 选项会很好。

从我们目前所看到的,似乎 XMLC 使修改整个段落变得容易,但更改单个单词却很困难。但是,XMLC 利用了 HTML “span” 标签,该标签采用 id 属性,并允许我们识别我们可能想要修改的单个单词、字符和图像。例如

<P id="para">This is a paragraph,
    <span id="phrase">and this is a phrase</span>.
</P>

当我们使用 XMLC 编译此 HTML 时,我们将能够使用 SetTextPara() 方法修改整个段落的内容,并使用 SetTextPhrase() 方法修改单个短语的内容。

Servlet

既然我们已经了解了如何从命令行使用 XMLC,让我们看看一个 servlet,它可以完成相同的任务。首先,我们的简单 PrintFooServlet servlet 将接收一个 HTTP 请求,并将返回文档的副本。

列表 2 包含显示 foo.html 的 servlet 的副本。与其命令行对应物一样,它创建了我们的 “foo” 类的实例,修改了它的一些文本,然后将 XML 树的文本表示形式写入输出流。然而,在这种特殊情况下,输出流连接到用户的浏览器。因此,用户看到了修改后的模板,而不知道涉及了两个 Java 类(和一个原始 HTML 文档)。

列表 2. PrintFooServlet.java

为了使我们的 servlet 工作,我需要将 foo.class 的副本放在 Jakarta-Tomcat servlet 引擎的 CLASSPATH 环境变量下的目录中。我选择将其放在 $TOMCAT/classes 的顶层。如果这是一个生产类,我无疑会希望将其放在更智能的位置,利用 Java 的分层命名空间。但是,我执行 xmlc 时没有指定包,这意味着 foo.class 必须放在顶层命名空间中。为了将 foo.class 放在 il.co.lerner 命名空间中,我必须使用 -class 选项

$ENHYDRA/bin/xmlc -class il.co.lerner.foo\
-parseinfo -verbose -keep foo.html

将 foo.class 放在 $TOMCAT/classes 中后,我成功编译了 PrintFooServlet.java。现在唯一剩下的挑战是执行此 servlet 并显示我修改后的 HTML 页面。再一次,我需要修改 CLASSPATH,但这次需要更改的 CLASSPATH 是 Tomcat servlet 引擎的 CLASSPATH,它代表我们执行 servlet。我修改了 $TOMCAT/bin/tomcat.sh,以便在其导出 CLASSPATH 之前,我们添加三个 Enhydra 提供的 .jar 文件并重新启动 Tomcat。在将我的浏览器指向 servlet 后不久,我很高兴在屏幕上看到了原始 HTML 文件的修改版本。

数据库和 XMLC

很容易看出我们如何可以使用从关系数据库中获取的信息来填充页面。例如,这是一个小的 PostgreSQL 表,我们可以用它来存储每个日历日的不同格言

CREATE TABLE DailySayings (
    date   TIMESTAMP NOT NULL,
    saying TEXT NOT NULL,
    UNIQUE(date)
)

现在让我们在我们的系统中插入一些格言

INSERT INTO DailySayings(date, saying)
VALUES (CURRENT_DATE,
     'A bird in the hand is worth two in the bush.');
INSERT INTO DailySayings(date, saying)
VALUES (CURRENT_DATE+1,
     'A penny saved is a penny earned.');
INSERT INTO DailySayings(date, saying)
VALUES (CURRENT_DATE+2,
     'The rain in Spain falls mainly in the plain.');
要检索今天的格言,我们只需要以下查询
SELECT saying
FROM DailySayings
WHERE date = CURRENT_DATE
为了编写一个显示今天格言的 servlet,我们将需要两个类:一个我们将使用 XMLC 创建的模板(saying.html,它将被编译为 saying.class)和另一个将加载和操作模板的类 (DailySaying.java)。我们将在编写 XMLC 文档和我们的操作类之前约定,id “saying” 将两者链接在一起。

我们的 XMLC 文档非常简单明了

<html>
   <head><title>Today's saying</title></head>
   <body>
      <h1>Today's saying</h1>
      <p>And now, as you requested, today's saying:
        <span id="saying">Saying Goes Here</span>.</p>
   </body>
</html>

我将此 HTML 文档编译为 Java 类 il.co.lerner.saying,并将 .java 文件保留下来只是为了好玩

$ENHYDRA/bin/xmlc -class il.co.lerner.saying\
-parseinfo -verbose -keep saying.html
然后,我将生成的 saying.class 文件复制到 $TOMCAT_HOME/classes/il/co/lerner 中,我在其中保留我的 servlet 相关类。

安装我的文档后,我必须编写一个操作类。此类执行我们上面看到的 SQL 查询,检索结果并将其粘贴到我们编译的 XMLC 文档中。列表 3 [请参阅列表 3,网址为 ftp://ftp.linuxjournal.com/pub/lj/listings/issue88/] 包含我们的 servlet 的源代码,我将其编译并放入我的 Tomcat 服务器上的活动 servlet 上下文中。重新启动 Tomcat 和 Apache 后,我能够通过我的 Web 浏览器检索今天的格言,并将 SQL 结果实例化到 HTML 文档中。

XMLC 是一个好的解决方案吗?

当我第一次开始研究 XMLC 时,我对它的可行性表示严重怀疑。在使用了多年的混合模板之后,将 HTML 文件转换为 Java 类,然后仅使用 DOM 操作该类,这似乎太奇怪了。事实上,启动 DOM 解析器比简单地显示文件需要更多的资源。

然而,随着我开始使用 XMLC,我越来越意识到它相对于此类模板的优势。本质上,XMLC 迫使设计人员和开发人员在其文档和程序之间创建合同或 API 规范。一旦此 API 就位,就无法轻易更改,这不一定是坏事。最重要的是,设计人员和开发人员之间 API 的稳定性使他们能够并行工作,几乎不干扰彼此的工作。

由于 Java 操作类可以以任何它选择的方式修改编译文档的 HTML,我们可以很容易地想象这样一种情况:我们一次引入三个类:一个头文件、文档的主体和一个页脚文件。然后我们的类可以使用 DOM 方法将页眉附加到文档的开头,并将页脚附加到文档的结尾。通过这种方式,我们可以为我们的站点添加全局格式,而无需将样板文本复制到每个文件的顶部。

当然,在使用 XMLC 时,有很多令人恼火的细节。其中之一是为每个 HTML 文件编写一个 servlet 很快就会变得乏味和令人沮丧。诚然,我们可以编写一个 servlet,它在其查询字符串中获取文件名,几乎充当 XMLC 创建的各种类的文档模板。也许我还没有充分探索 Enhydra 以发现这个问题的答案,也许 Enhydra 开发人员很快就会习惯于为他们想要显示的每个页面创建两个 Java 类。无论如何,即使在中小型站点上,这也会很快创建大量的类。

我认为 XMLC 最大的问题是缺乏用于操作 HTML(以及 XML)的高级 API。XMLC 的常见问题解答之一是“如何向 HTML 表格添加行?” 这样的任务,使用标准 HTML 可以轻松完成,但使用 XMLC 很快就会成为负担。您必须首先找到要添加行的表格底部,然后将单个节点(和属性)添加到该节点。它具有非常不 HTML 的感觉,并迫使开发人员在想要以 HTML 术语思考时考虑节点。鉴于 Enhydra 包含一个使用 Java 方法创建 SQL 查询的 API,我想象一个类似的 HTML 操作 API 不会太困难。

结论

XMLC 是一项有趣的技术,它位于 Enhydra 应用服务器的核心。XMLC 迫使开发人员(和设计人员)在开始工作之前考虑他们将如何交互,然后允许他们独立工作。虽然这种操作模式可能会让经验丰富的模板用户感到不适应,但它很快就会成为第二天性,并且感觉比我预期的更自然。

事实上,Zope 的 ZPT 使用类似的方法来分离形式和内容,这可能表明 Web 开发社区内的一种趋势。我们可以期望在不久的将来看到更多类似 XMLC 的系统。如果我们幸运的话,也许甚至会对这些模板进行一些标准化,以便设计人员可以在系统之间迁移,而无需学习它们之间的细微差别。

虽然 XMLC 很重要,但 Enhydra 还有许多其他值得研究的功能。下个月我们将继续研究 Enhydra,着眼于它如何加速服务器端数据库应用程序的编写。

资源

XMLC
Reuven M. Lerner 拥有一家小型咨询公司,专门从事 Web 和互联网技术。他与妻子 Shira 和女儿 Atara Margalit 住在以色列的 “未来之城” Modi'in。您可以通过 reuven@lerner.co.il 与他联系,或访问 ATF 主页 http://www.lerner.co.il/atf/
加载 Disqus 评论