与 HTML 相比,XML 的优势之一在于其可扩展性。有了这种特性,XML 就可以用 HTML 中不可能的方式来描述信息。本月,Todd 演示了如何构建用 Java 处理 XML 的框架,同时,适当地结合了两种语言的固有可扩展性。
介绍
我在上个月展示了有关 XML在企业中地位的案例(有关上月栏目,请参阅 参考资料)。除了 XML的发布方面之外,我还有意地试图集中讲解了应用集成和数据交换问题。我演示了使用通常可用的Java 工具来对 XML做语法分析和验证有多容易,还将这些方法与更传统的特定方法作了比较。
本月,我要沿这条思路深入探索--语法分析和确认到目前为止还不错,但还不是很成功。目前的问题通常涉及到对做过语法分析的信息进行某些处理。但是,如果不理解用于生成信息的标记,该怎么办?跟我一起沿着Java 和 XML 的边界再前进一点,我还将向您展示如何使用 Java来解决这个问题。
XML标记:怎么办?
让我们直入问题的中心。需要处理的 XML 特性是其定义新标记的能力。XML的标记说明与其相关的内容含义(以及有关其它标记)的一些事项。因为XML 中的标记集是开放的(它与 HTML 不同,HTML中的标记集是封闭的),所以,不可能构建可以立即处理整个标记集的应用。这就为这种处理引入了一点不确定性。到底如何处理不理解的标记呢?应用程序可以忽略新标记。这是在浏览器战争最激烈时,浏览器供应商通常采用的方法。领先的浏览器供应商喜欢在其产品的每一个发行版中定义新标记,而每个浏览器版本都默默地忽略那些不理解的标记。这种方法很安全,但并不令人满意。 组织可以将标记集标准化。这种方法聪明地回避了整个问题。定义一个标准标记集和一个文档类型定义(DTD),然后拒绝任何不符合该模型的XML。对很多问题而言,这确实是正确的解决方案。例如,销售订单就符合良好定义的模式。如果允许电子贸易伙伴定义新标记(至少是没有约束-这可能成为某些具有良好约束的标记定义(如宏)的适用性的理由),那么什么也得不到。不幸的是,不是所有的应用程序(例如XML 浏览器)都适合这种方法。 应用程序可以试着想出如何处理新标记。浏览器和内容显示工具、以及其它通用的 XML工具必须在出现新的、但却有效的标记时正确操作。有几种方法来解决这种问题。可扩展样式表语言(XSL) 正是这样一种尝试。XSL提供一个转换工具箱,该工具箱允许定义从不理解的标记集到所理解的标记集合的映射或转换(例如,从XML 到 HTML)。但是,这种解决方案也有其限制。 可以构建新的框架。虽然以上每一个解决方案都存在,但是,我们将全面讨论另一种解决方案。我们的解决方案要求允许浏览器或XML工具寻找并下载设计成处理新标记的代码,然后将这些代码集成到应用程序中。要做到这点,我们将构建新的框架。
在讨论构建这种解决方案的事项之前,需要对 XML再多了解一点。特别是,需要理解如何在应用程序中操纵XML。需要理解文档对象模型 (DOM)。
DOM
DOM 是独立于平台、中立于编程语言的API,它允许程序从应用中访问和修改 XML 文档的内容和结构。
作为其核心,DOM定义了代表构成 XML文档的所有对象的一系列类型:元素、属性、实体引用、注释、文本数据、处理指示和其余对象。(本文使用 对象这个词来泛指 XML 文档的组成部件。)DOM最初被认为是存在于浏览器中,现在已有了广泛得多的作用。还需要指出的是:DOM不是特定于 XML 的。它同样很好地适用于 HTML。
要领会DOM,需要回想一下,XML的一个重要特征是其可以将很多文档表示成内容和标记的层次结构的概念。例如,以下代码代表有效的XML 文档:<html>
<head>
<title>
This is the title.
</title>
</head>
<body>
<h1>
This is a headline.
</h1>
This is the body.
And this is more of the body.
</body>
</html>
我不想为您提供 XML初级读物,但是确实想声明一点:XML(和HTML)的一个重要需求就是:标记必须正确 嵌套--不能重叠。因此,认为 <one><two></two></one> 是有效的 XML,而 <one><two></one></two> 不是。结果,良好定义的 XML文档完好地映射成树状的数据结构。下一步,可以将上面的文档变换成下面图1 中的树。
图 1. XML 树
DOM提供我们所需的、与 XML 文档中的元素和内容动态交互的机制。考虑图 1中的树,已经将构成初始 XML 文档的标记(按 DOM的说法,是 元素)映射成图 1 中的树节点。
每个标记都在括起的标记和作为整体的文档上下文中有意义。再考虑一下这些标记。它们清楚地定义了浏览器中与显示相关的元素。就这样,它们有与其相关的易于理解的行为。我们希望浏览器知道如何在浏览器窗口中绘出它们。浏览器中有实现这种行为的代码。
现在,考虑下面的代码,这段代码表示具有新标记的 XML文档:
<mail>
<from>friend</from>
<to>friend</to>
<subject>GET RICH QUICK!!!</subject>
<body>
Dear Friend,
PLEASE READ THIS!!! It's easy to make money on the Internet.Just
follow this proven three-step plan.
</body>
</mail>
这又是一小段XML。我们明确不指望浏览器知道如何处理这些标记。要处理它们,必须修改浏览器(或者其它通用XML 应用)。
下面的图 2显示了用来实现该代码的通用框架。
图 2. 修改浏览器的框架
在这个示例中,左边DOM 层次结构的每一个元素都映射成右边层次结构的元素。左边的 DOM元素代表文档的结构。右边的元素代表结构元素的行为。将行为元素也排列成层次结构,以便它们可以用反映DOM 模型结构的方式彼此交互。
Hook类
行为层次结构的组成部件是 Hook 类。该类提供到无行为DOM 树的行为“挂钩”。下面是 Hook 类的代码:import java.util.Vector;
import java.util.Enumeration;
import org.w3c.dom.Element;
public
class Hook {
private
Hook _hookParent = null;
private
Vector _vectorChildren = new Vector();
private
Element _element = null;
public
void
setElement(Element element) {
_element = element;
}
protected
Element
getElement() {
return _element;
}
public
void
setParent(Hook hookParent) {
_hookParent = hookParent;
}
protected
Hook
getParent() {
return _hookParent;
}
public
void
addChild(Hook hookChild) {
_vectorChildren.addElement(hookChild);
}
protected
Enumeration
getChildren() {
return _vectorChildren.elements();
}
public
Object
build(Object object) {
object = doOnNodeStart(object);
Enumeration enumeration = _vectorChildren.elements();
while (enumeration.hasMoreElements()) {
Hook hook = (Hook)enumeration.nextElement();
hook.build(object);
}
object = doOnNodeEnd(object);
return object;
}
public
Object
doOnNodeStart(Object object) {
return object;
}
public
Object
doOnNodeEnd(Object object) {
return object;
}
}
Hook 意思是相关子类型系列的超类型。 Hook 类本身不实现任何行为。实际上,除了链接到父类和子类之外,它不实现任何方法。子类型系列应该在这些原语上构建,并基于公共行为体系结构定义类的集合。
Filter类
Filter 类的唯一方法从 DOM树递归构建行为树。
让我们看一下 Filter 类:
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public
class Filter {
public
void
filter(Hook hookParent, NodeList nodelist) {
if (nodelist != null) {
for (int i = 0; i < nodelist.getLength(); i++) {
Node node = nodelist.item(i);
// This handles what appears to be a bug in at least one
// vendor's implementation (IBM's xml4j v. 2.0.6) of the
// DOM. This bug seems to effect text nodes: the reported
// length is nonzero but the returned list contains no valid
// elements.
if (node == null) break;
if (node instanceof Element) {
Element element = (Element)node;
Hook hook = HookManager.createHook(element);
filter(hook, element.getChildNodes());
hook.setParent(hookParent);
hookParent.addChild(hook);
} else {
filter(hookParent, node.getChildNodes());
}
}
}
}
}
HookManager 类
HookManager 类动态装入实现必要行为的代码(类 Hook 的子类型)。
下面是 HookManager 类的代码:
import org.w3c.dom.Element;
public
class HookManager {
public
static
Hook
createHook(Element element) {
Hook hook = null;
try {
hook = (Hook)Class.forName("_" +element.getTagName()).newInstance();
hook.setElement(element);
} catch (ClassNotFoundException exception) {
hook = new Hook();
hook.setElement(element);
} catch (IllegalAccessException exception) {
exception.printStackTrace(System.err);
} catch (InstantiationException exception) {
exception.printStackTrace(System.err);
}
return hook;
}
}
示例
这个示例使用上面所述的框架将 描述用户界面的 XML转换换成实际的用户界面。首先,这里有一段 XML。已包括了DTD,以进一步说明元素之间的关系:
<?xml version="1.0"?>
<!DOCTYPE GUI [
<!ELEMENT GUI
(FRAME)+
>
<!ELEMENT FRAME
( PANEL | BUTTON )*
>
<!ATTLIST FRAME
LAYOUT ( Border | Flow ) "Border"
TITLE CDATA "Frame"
>
<!ELEMENT PANEL
( PANEL | BUTTON )*
>
<!ATTLIST PANEL
PLACEMENT ( North | South | East | West | Center | Default )"Default"
LAYOUT ( Border | Flow ) "Flow"
>
<!ELEMENT BUTTON
EMPTY
>
<!ATTLIST BUTTON
PLACEMENT ( North | South | East | West | Center | Default )"Default"
LABEL CDATA "Button"
>
]>
<GUI>
<FRAME TITLE="One">
<PANEL PLACEMENT="North">
<BUTTON LABEL="North1"/>
<BUTTON LABEL="North2"/>
</PANEL>
<PANEL PLACEMENT="South">
<BUTTON LABEL="South"/>
</PANEL>
<PANEL PLACEMENT="Center">
<BUTTON LABEL="Center"/>
</PANEL>
</FRAME>
<FRAME TITLE="Two">
<PANEL PLACEMENT="North">
<BUTTON LABEL="North"/>
</PANEL>
<PANEL PLACEMENT="South">
<BUTTON LABEL="South"/>
</PANEL>
<PANEL PLACEMENT="Center" LAYOUT="Border">
<PANEL PLACEMENT="North" LAYOUT="Border">
<BUTTON PLACEMENT="North" LABEL="Center:North:North"/>
</PANEL>
<PANEL PLACEMENT="South" LAYOUT="Border">
<BUTTON PLACEMENT="South" LABEL="Center:South:South"/>
</PANEL>
<PANEL PLACEMENT="Center" LAYOUT="Border">
<BUTTON PLACEMENT="North"LABEL="Center:Center:North"/>
<BUTTON PLACEMENT="South"LABEL="Center:Center:South"/>
<BUTTON PLACEMENT="Center"LABEL="Center:Center:Center"/>
</PANEL>
</PANEL>
</FRAME>
<FRAME TITLE="Three" LAYOUT="Flow">
<BUTTON LABEL="A"/>
<BUTTON LABEL="B"/>
</FRAME>
</GUI>
在 jar 文件 howto.jar中包括了源码和编译过的类文件。样本XML 文件是 test.xml。要使用该软件,还需要IBM XML4J 库的一个副本,可以 jar 格式的 xml4j.jar获得。我正在经 IBM 的 XML4J商业特许分发 XML4J。(请参阅 参考资料,以zip 文件形式下载完整源码。)
通过以下步骤运行软件:下载上述文件 将 howto.jar 和 xml4j.jar 添加到 CLASSPATH 从命令行,输入: java Maintest.xml
应用程序将读取指定的 XML文件,分析其语法并进行确认,动态装入和创建必需的挂钩,并创建用户界面。
结束语
当需要动态扩展应用时,Java动态装入类的能力显得极为方便。它使得如我前面所讲那样的框架成为可能,并真正扩展了如XML 的语言的潜力。从前面所示的框架和样本应用程序,您可以看到,使用XML作为脚本用户界面定义语言,或者作为配置工具或任何其它一些可能性的基础有多简单。如果您提出新的应用,请写信告诉我。
下个月,将继续讨论XML 和 Java的兼容性。我将演示如何将脚本语言集成到我们所创建的框架,以及如何动态修改DOM 树本身。
关于作者
自从方便的台式机型号出现以来,Todd Sundsted就一直在编写程序。尽管最初对用 C++构建分布式对象应用感兴趣,但是,当 Java成为同类语言中明显的选择时,Todd 转到了 Java编程语言。除了写文章之外,Todd 还是 ComFrame Software公司的设计师。