文档对象模型
eric armstrong
sax一章中已经编写了包含用于放映的幻灯片的xml文件。然后又使用sax api将xml回送到显示器。
本章中,将使用文档对象模型(dom)建立一个小的slideshow应用程序。首先构建dom并查看它,然后看看如何编写xml结构的dom ,将它显示在gui中,并且操作树结构。
文档对象模型是普通的树结构,每个节点包含一个来自于xml结构的组件。两种最常见的节点类型是元素节点(element node)和文本节点(text node)。使用dom函数能够创建节点、删除节点、改变节点内容并且遍历节点层次。
本章将解析现有的xml文件以构建dom,显示并查看dom的层次结构,将dom转换成能够友好显示的jtree,并且展现命名空间的语法。你将从头开始创建一个dom,并且了解如何使用sun的jaxp实现的一些特定实现性能将现有的数据集转换成xml。
首先要确信,在你的应用程序中dom是最好的选择。下一节何时使用dom将具体介绍它。
——————————————————————————–
注意:可以在下面的页面中找到本章的例子<jwsdp_home>/docs/tutorial/examples/jaxp/dom/samples.
何时使用dom
文档对象模型 (dom) 是一个文档标准(例如,文章和书)。另外,jaxp 1.2实现支持xml schema,在任何给定的应用程序中必须慎重考虑这一点。
另外,如果你正在处理简单数据结构,并且xml schema不是你计划的一部分,那么你可能会发现一个面向对象的标准如jdom 和dom4j 更加适合于实现你的目标。
dom从一开始就是一种与语言无关的模型。这是因为它专门用于像c或perl这类语言,没有利用java的面向对象的性能。这个事实再加上文档/数据定义,也能帮助解释处理dom和处理jdom或dom4j结构之间的区别。
本节中,将讨论遵守这些标准的模型之间的区别,以便能够帮助你为应用程序选择最合适的模型。
文档vs.数据
dom中使用的文档模型和jdom或dom4j中使用的数据模型的主要区别在于:
· 层次结构中的节点类型
· “混合内容”的性能
正是构成数据层次结构的 “节点” 的不同造成了这两个模型之间编程的区别。然而,只有混合内容的性能能解释不同标准定义“节点”有何区别。所以,首先来看看dom的“混合内容模型”。
混合内容模型
回想在对文档-驱动编程 (ddp) 的讨论中,可以将文本和元素自由结合到dom层次结构中。这种结构被称为dom模型中的“混合内容”。
文档中经常出现混合内容。例如,为了表示该结构:
<sentence>this is an <bold>important</bold> idea.</sentence>
dom节点的层次结构看起来跟下面的很像,其中每行代表一个节点:
element: sentence
+ text: this is an
+ element: bold
+ text: important
+ text: idea.
注意,句元素(sentence element)包含文本,后面跟着子元素,再后面是其他文本。 “混合内容模型”是通过将文本和元素混合起来而定义的。
节点种类
为了提供混合内容的性能,dom节点生来就非常简单。例如,在前面的例子中,第一个元素的内容 (它的value)简单地标识了节点的类型。
dom用户第一次通常都会被这个事实迷惑。到访过<sentence> 节点后,他们就想要节点的“内容”,并且想得到其他有用信息。但是,它们得到的是元素的名字“sentence”。
注意:dom node api定义了nodevalue()、node.nodetype()和nodename() 方法。对于第一个元素节点,nodename() 返回 "sentence",而nodevalue() 返回空。对于第一个文本节点, nodename() 返回"#text",并且nodevalue() 返回"this is an "。很重要的一点是元素的value 和它的content不一样。
——————————————————————————–
处理dom时,获得关心的内容意味着要检查节点包含的子元素列表,忽略那些不感兴趣的,只处理那些感兴趣的。
例如,在上面的例子中,如果要查询句子的“text(文本)”意味着什么?根据你的应用程序,下面的每一条都是合理的:
· this is an
· this is an idea.
· this is an important idea.
· this is an <bold>important</bold> idea.
简单模型
使用dom可以方便地创建所需的语义。然而,也需要经过必要的处理,以实现这些语义。而像jdom和dom4j这样的标准,使得事情更加简单,因为层次结构中的每个节点都是一个对象。
虽然jdom 和dom4j 允许使用混合内容元素,但是它们主要不是用于这类情况的。相反,它们主要用于xml结构包含数据的应用程序中。
如传统数据处理中所介绍的,数据结构中的元素包含文本或其他元素,但是不能两个都包含。例如,下面的xml表示简单的通讯簿:
<addressbook>
<entry>
<name>fred</name>
<email>fred@home</email>
</entry>
…
</addressbook>
——————————————————————————–
注意: 对于这类非常简单的xml数据结构,也可以使用java平台1.4版本中的正则表达式包(java.util.regex)。
——————————————————————————–
在jdom 和dom4j中,一旦到达包含文本的元素,就会调用一个方法如text() 来获得它的内容。处理dom时,必须查看子元素列表,将节点的文本“放在一起”,就如前面所见到的——即便该列表只包含一项(text 节点)。
所以,对于简单的数据结构(如前面的通讯簿),使用jdom或dom4j可以节省很多工作。即便是数据在技术上是“混合”的,也可以使用这些模型中的某个模型,除非给定节点有一个(并且仅有一个)文本段。
下面是这类结构的一个例子,它在jdom或dom4j中也很容易处理:
<addressbook>
<entry>fred
<email>fred@home</email>
</entry>
…
</addressbook>
这里,每项都有标识文本,并且后面还有其他元素。使用该结构,程序能够浏览一个项,调用text() 来查看它属于谁,并且如果<email> 子元素在当前节点下,就处理它。
增加复杂度
但是要完全理解查找或操作dom时需要进行的处理,就必需知道dom包含的节点。
下面的例子主要就是针对这点的。它是该数据的一个表示法:
<sentence>
the &projectname; <![cdata[<i>project</i>]]> is
<?editor: red><bold>important</bold><?editor: normal>.
</sentence>
该句子包含一个实体引用——一个在其他地方定义的指向“实体”的指针。这里,实体包含项目名。这个例子也包含cdata段(未解释的数据,如html中的<pre> 数据),和处理指令 (<?…?>)。在本例中该处理指令告诉编辑器在绘制文本时使用哪种颜色。
下面是该数据的dom结构。它代表了强壮的应用程序要处理的结构:
+ element: sentence
+ text: the
+ entity ref: projectname
+ comment: the latest name were using
+ text: eagle
+ cdata: <i>project</i>
+ text: is
+ pi: editor: red
+ element: bold
+ text: important
+ pi: editor: normal
本例描绘了可能在dom中出现的节点。虽然在绝大多数时候你的应用程序能够忽略它们中的大部分,但是真正强壮的实现需要识别并处理它们中的每一个。
同样,到访节点的处理涉及到子元素的处理,它忽略你不关心的元素仅仅查看关心的元素,直到找到感兴趣的节点。
通常,在这类情况下,仅对找到包含指定文本的节点感兴趣。例如,在dom api 中,有一个例子,那个例子中希望找到一个<coffee> 节点,该节点的<name> 元素包含文本“mocha java”。要执行该查找,程序需要处理<coffee> 元素列表,并且对于每一个列表:a)获取它下面的<name> 元素。b)检查该元素下的text 节点。
然而,该例是建立在一些简单的假设之上的。它假设处理指令、注释、cdata节点和实体引用不能存在于数据结构中。许多简单的应用程序能够去掉这些假设。另外,真正强壮的应用程序需要处理各类有效的xml数据。
(仅当输入数据包含所需的简化的xml结构时,“简单”的应用程序才能运行。但是没有任何验证机制能够确保不存在更加复杂的结构。毕竟,xml是专门为它们设计的。)
要变得更加强壮,dom api中的例子代码必须完成以下事情:
1. 搜索<name> 元素时:
a. 忽略注释、属性和处理指令。
b. 允许出现<coffee> 子元素没有按指定的顺序出现的情况。
c. 如果没有验证,跳过包含可忽略空白的text 节点。
2. 提取节点的文本时:
a. 从cdata 节点和文本节点提取文本。
b. 收集文本时忽略注释、属性和处理指令。
c. 如果遇到了实体引用节点或另一个元素节点,递归。(也就是说,将文本提取过程应用于所有的子节点)。
——————————————————————————–
注意:jaxp 1.2解析器不将实体引用节点插入dom。相反,它插入包含引用内容的text 节点。另外,建立在1.4平台中的jaxp 1.1解析器并不插入实体引用。所以,所以独立于解析器的强壮的实现需要能处理实体引用节点。
当然,许多应用程序不用担心这类事情,因为它们看到的数据受到了严格的控制。如果数据能够来自于各类外部源,应用程序就必须考虑这些可能性。
在dom教程搜索节点 和获得节点内容的结尾处给出了执行这些函数的代码。现在,目标就是确定dom是不是适合于你的应用程序。
选择模型
可以看到,使用dom时,即便是像得到节点中的文本这样简单的操作也要进行大量的编码。所以,如果程序将处理简单数据结构,jdom、dom4j甚至1.4正则表达式包(java.util.regex)将更能满足你的需求。
另外,对于完备的文档和复杂的应用程序,dom 提供了大量灵活性。并且如果需要使用xml schema,那么至少dom是可以使用的方法。
如果你要在开发的应用程序中处理文档和数据,那么dom可能仍然是最好的选择。毕竟,一旦编写了检查和处理dom结构的代码,自定义它以实现特定的目的就很容易了。所以,选择在dom中完成所有的事情,只需要处理一个api集合而不是两个。
还有,dom标准是标准的。它很强壮且完整,并且有许多实现。这是许多大型安装的决定因素——特别是对产品应用程序,以避免在api发生改变时进行大量的改写。
最后,虽然目前不允许对通讯簿中的文本加粗、倾斜、设置颜色和改变字体大小,某一天可能还是需要处理这些。由于实际上dom将处理你抛给它的所有内容,选择dom能够更加容易生成“能适应未来(future-proof)”的应用程序。
将xml数据读入到dom中
本节将通过读取现有xml文件来构建一个文档对象模型 (dom)。在下面几节,你将会学习如何在swing树组件中显示xml并且练习操作dom。
——————————————————————————–
注意:在教程的下一节转换xml样式表语言中,你将看到如何写出作为xml文件的dom。(你也将看到如何相对简单地将现有的数据文件转换成xml)
——————————————————————————–
创建程序
文档对象模型(dom)提供了创建节点、修改节点、删除及重新组织它们的api。在本教程的第五节创建和操纵dom中可以看到,创建dom相对比较容易。
然而,在创建dom之前,要先了解dom的结构。这系列练习通过在swing jtree中显示dom,以便能看到dom的内部。
创建框架
既然已经对如何创建dom有了大致了解,现在来建立一个简单程序来将xml文档读入dom,然后在回写。
——————————————————————————–
注意:本节讨论的代码在domecho01.java中。它操作在文件 slidesample01.xml 上。(可浏览的版本是 slidesample01-xml.html)
——————————————————————————–
先来看看应用程序的基本逻辑,确保命令行上带有一个参数:
public class domecho {
public static void main(string argv[])
{
if (argv.length != 1) {
system.err.println(
"usage: java domecho filename");
system.exit(1);
}
}// main
}// domecho
导入所需的类
本节中将看到所有的类都是单独命名的。这样,在引用api文档时就能知道每个类的出处。在你自己的应用程序中,你可能希望使用短格式javax.xml.parsers.*来替换如下所示的导入语句。
添加如下代码以导入要使用的jaxp api:
import javax.xml.parsers.documentbuilder;
import javax.xml.parsers.documentbuilderfactory;
import javax.xml.parsers.factoryconfigurationerror;
import javax.xml.parsers.parserconfigurationexception;
想在解析xml文档时抛出异常,添加下面几行:
import org.xml.sax.saxexception;
import org.xml.sax.saxparseexception;
添加下面几行来读取示例xml文件并识别错误:
import java.io.file;
import java.io.ioexception;
最后,为dom和dom异常导入w3c定义:
import org.w3c.dom.document;
import org.w3c.dom.domexception;
——————————————————————————–
注意:仅在遍历或操作dom时才会抛出domexception 。使用下面介绍的不同机制来报告解析过程中出现的错误。
——————————————————————————–
声明 dom
org.w3c.dom.document 类是文档对象模型(dom)的w3c名。不管解析xml文档还是创建一个xml文档,都会产生一个文档实例。在教程的后面将在另一个方法中引用该对象,所以,在这里将它定义成全局对象:
public class domecho
{
static document document;
public static void main(string argv[])
{
它必须是static的,因为你需要在很短时间内从main方法生成它的内容。
处理错误
下一步,插入错误处理逻辑。该逻辑基本上跟sax教程的使用非验证解析器处理错误中的代码相同,所以在这里就不详细介绍了。在这里要特别提醒的是,当解析xml文档遇到困难时,需要jaxp-conformant 文档构造器来报告sax异常。实际上dom解析器不需要在内部使用sax解析器,但是由于早就有了sax标准,看来可以用它来报告错误。结果,dom和sax应用程序的错误处理代码就非常简单:
public static void main(string argv[])
{
if (argv.length != 1) {
…
}
try {
} catch (saxparseexception spe) {
//
error generated by the parser
system.out.println("\n** parsing error"
+ ", line " + spe.getlinenumber()
+ ", uri " + spe.getsystemid());
system.out.println(" " + spe.getmessage() );
// use the contained exception, if any
exception x = spe;
if (spe.getexception() != null)
x = spe.getexception();
x.printstacktrace();
} catch (saxexception sxe) {
//
error generated during parsing
exception x = sxe;
if (sxe.getexception() != null)
x = sxe.getexception();
x.printstacktrace();
} catch (parserconfigurationexception pce) {
// parser with specified options cant be built
pce.printstacktrace();
} catch (ioexception ioe) {
// i/o error
ioe.printstacktrace();
}
}// main
实例化工厂
接着,添加下面的代码,得到工厂的一个实例,该实例能够给出一个文档构造器:
public static void main(string argv[])
{
if (argv.length != 1) {
…
}
documentbuilderfactory factory =
documentbuilderfactory.newinstance();
try {
获得解析器并解析文件
现在,添加下面的代码以获得构造器的一个实例,并用它来解析指定的文件:
try {
documentbuilder builder = factory.newdocumentbuilder();
document = builder.parse( new file(argv[0]) );
} catch (saxparseexception spe) {
保存该文件!
到现在,你可能觉得启动每个jaxp应用程序的方法几乎相同。的确如此!保存该版本的文件,将它作为模板。后面将要使用它,它是xslt转换应用程序的基础。
运行程序
在整个dom教程中,将使用sax段中看到的示例幻灯片显示。尤其是,你将使用slidesample01.xml(它是一个简单的xml文件,内部没有什么内容)和slidesample10.xml(是一个更加复杂的例子,引入了dtd、处理指令、实体引用和cdata段)。
关于如何编译并运行程序的说明,请查看sax教程的编译和运行程序 。以"domecho"替代"echo"作为程序的名字。
在slidesample01.xml上运行该程序。如果运行没有出错,你就成功地解析了一个xml文档并且构建了一个dom。恭喜!
——————————————————————————–
注意:目前你只能听从我的建议,因为这时你没有任何办法来显示结果。但是在不久的将来…
——————————————————————————–
其他信息
既然你已经成功地读取了一个dom,要高效使用documentbuilder 必须知道一到两点。即:
· 配置factory
· 处理验证错误
配置工厂
默认情况下,工厂返回非验证解析器,对命名空间一无所知。要获得一个验证解析器,并且/或能理解命名空间,请使用下面的命令来设置一个或多个参数,以配置该工厂:
public static void main(string argv[])
{
if (argv.length != 1) {
…
}
documentbuilderfactory factory =
documentbuilderfactory.newinstance();
factory.setvalidating(true);
factory.setnamespaceaware(true);
try {
…
——————————————————————————–
注意:不要求jaxp-conformant解析器支持这些参数的所有的结合,虽然引用解析器支持。如果你指定了一个无效的参数结合,在获得解析器实例时factory产生parserconfigurationexception 。
——————————————————————————–
在dom教程的最后一节使用命名空间中将详细介绍如何使用命名空间。本节要结束了,虽然你仍然希望知道…
处理验证错误
记住,在你阅读sax教程时,你所要做的事情就是构建一个dom?这里,那些信息起作用了。
记住对验证错误的默认反应(由sax标准提出)就是什么不也做。jaxp标准要求抛出sax异常,所以使用跟sax应用程序中几乎相同的错误处理机制。尤其需要使用documentbuilder的seterrorhandler方法提供实现sax errorhandler 接口的对象。
——————————————————————————–
注意:也可以使用documentbuilder 中的setentityresolver 方法。
——————————————————————————–
下面的代码使用匿名内部类来定义errorhandler。下面的代码确保验证错误产生异常。
builder.seterrorhandler(
new org.xml.sax.errorhandler() {
// ignore fatal errors (an exception is guaranteed)
public void fatalerror(saxparseexception exception)
throws saxexception {
}
// treat validation errors as fatal
public void error(saxparseexception e)
throws saxparseexception
{
throw e;
}
// dump warnings too
public void warning(saxparseexception err)
throws saxparseexception
{
system.out.println("** warning"
+ ", line " + err.getlinenumber()
+ ", uri " + err.getsystemid());
system.out.println(" " + err.getmessage());
}
);
本段代码使用匿名内部类来产生实现errorhandler 接口的对象实例。由于它没有类名字,所以是“匿名”的。你可以将它看作"errorhandler"实例,虽然技术上它是实现指定接口的没有名字的实例。这些代码本质上跟使用非验证解析器处理错误中的相同。要了解验证问题的更多知识,请参照使用验证解析器。
展望未来
下节中将在jtree中显示dom结构,并且开始展示它的结构。例如,你将看到实体引用和cdata段是如何出现在dom中的。可能更加重要的是,你将看到如何将文本节点(它包含实际的数据)放在dom中的元素节点下。
显示dom层次结构
要创建或操纵一个文档对象层次结构 (dom),必须要清楚地了解dom中节点的结构。本节中将展示dom的内部结构。
回送树节点
现在你所需要的是展示dom中节点的方法,这样你就能看到节点内究竟包含什么。要实现这一点,将dom转换成jtreemodel 并且在jtree中显示完整的dom。这些需要大量的工作,但是它不仅将产生你将来要使用的诊断工具,而且可以帮助你了解dom结构。
将domecho 转换为gui 应用程序
由于dom是树,并且swing jtree 组件都跟显示树有关,所以有必要将dom放入jtree,以便查看。该过程的第一步是处理domecho 程序,让它成为一个gui 应用程序。
注意:本节讨论的代码在domecho02.java中。
添加import语句
首先导入建立应用程序和显示jtree需要的gui组件:
// gui components and layouts
import javax.swing.jframe;
import javax.swing.jpanel;
import javax.swing.jscrollpane;
import javax.swing.jtree;
dom教程的后面部分将设计dom显示,以产生jtree显示的用户友好版本。用户选择树中的元素时,子元素将显示在邻近的编辑器面板中。所以,进行安装工作时,需要导入建立分割视图(jsplitpane)和显示子元素文本(jeditorpane)所需的组件:
import javax.swing.jsplitpane;
import javax.swing.jeditorpane;
添加一些你需要的支持类:
// gui support classes
import java.awt.borderlayout;
import java.awt.dimension;
import java.awt.toolkit;
import java.awt.event.windowevent;
import java.awt.event.windowadapter;
最后,导入一些类来建立别致的边框:
// for creating borders
import javax.swing.border.emptyborder;
import javax.swing.border.bevelborder;
import javax.swing.border.compoundborder;
(这些是可选的。如果想要简化代码,可以跳过它们以及相关代码。)
创建gui框架
下一步是将应用程序转换为gui应用程序。为此,静态main方法将创建main类的一个实例,它将成为gui面板。
首先扩展swing jpanel 类,将该类变成一个gui面板:
public class domecho02 extends jpanel
{
// global value so it can be refd by the tree-adapter
static document document;
…
定义一些用来控制窗口大小的常量:
public class domecho02 extends jpanel
{
// global value so it can be refd by the tree-adapter
static document document;
static final int windowheight = 460;
static final int leftwidth = 300;
static final int rightwidth = 340;
static final int windowwidth = leftwidth + rightwidth;
现在,在main方法中,调用创建gui面板所在的外部框架的方法:
public static void main(string argv[])
{
…
documentbuilderfactory factory …
try {
documentbuilder builder = factory.newdocumentbuilder();
document = builder.parse( new file(argv[0]) );
makeframe();
} catch (saxparseexception spe) {
…
然后,需要定义makeframe方法本身。它包含创建框架的标准代码,并能很好地处理退出条件,给出main面板的实例,定制它的大小,最后将其定位于屏幕上并使之可视化:
…
} // main
public static void makeframe()
{
// set up a gui framework
jframe frame = new jframe("dom echo");
frame.addwindowlistener(new windowadapter() {
public void windowclosing(windowevent e)
{system.exit(0);}
});
//
set up the tree, the views, and display it all
final domecho02 echopanel = new domecho02();
frame.getcontentpane().add("center", echopanel );
frame.pack();
dimension screensize =
toolkit.getdefaulttoolkit().getscreensize();
int w = windowwidth + 10;
int h = windowheight + 10;
frame.setlocation(screensize.width/3 – w/2,
screensize.height/2 – h/2);
frame.setsize(w, h);
frame.setvisible(true)
} // makeframe
添加显示组件
将程序转换成gui应用程序的工作只剩下创建类构造函数并让它创建面板的内容。下面是构造函数:
public class domecho02 extends jpanel
{
…
static final int windowwidth = leftwidth + rightwidth;
public domecho02()
{
} // constructor
这里,使用前面导入的边框类建立常用边框(可选):
public domecho02()
{
// make a nice border
emptyborder eb = new emptyborder(5,5,5,5);
bevelborder bb = new bevelborder(bevelborder.lowered);
compoundborder cb = new compoundborder(eb,bb);
this.setborder(new compoundborder(cb,eb));
} // constructor
然后,创建一棵空树并且将它放入jscrollpane ,这样当它变大时,用户就能看到它的内容:
public domecho02(
{
…
//
set up the tree
jtree tree = new jtree();
// build left-side view
jscrollpane treeview = new jscrollpane(tree);
treeview.setpreferredsize(
new dimension( leftwidth, windowheight ));
} // constructor
现在,创建不可编辑的jeditpane ,它将最终保存选中的jtree 节点指向的内容:
public domecho02(
{
….
// build right-side view
jeditorpane htmlpane = new jeditorpane("text/html","");
htmlpane.seteditable(false);
jscrollpane htmlview = new jscrollpane(htmlpane);
htmlview.setpreferredsize(
new dimension( rightwidth, windowheight ));
} // constructor
构建了左边的jtree 和右边的jeditorpane 后,创建一个jsplitpane 来存放它们:
public domecho02()
{
….
// build split-pane view
jsplitpane splitpane =
new jsplitpane(jsplitpane.horizontal_split,
treeview, htmlview );
splitpane.setcontinuouslayout( true );
splitpane.setdividerlocation( leftwidth );
splitpane.setpreferredsize(
new dimension( windowwidth + 10, windowheight+10 ));
} // constructor
该代码建立了带有垂直分割线的jsplitpane 。它将在树和编辑器面板之间产生“水平分割线”。(实际上,更是水平布局)你也要设置分割线的位置,这样树能够得到它要的宽度,将剩下的窗口宽度分配给编辑器面板。
最后,指定面板的布局并且添加split pane:
public domecho02()
{
…
// add gui components
this.setlayout(new borderlayout());
this.add("center", splitpane );
} // constructor
恭喜!该程序现在是一个gui应用程序。现在可以运行它,看看屏幕上的总体布局是怎样的。为了引用,这里有完整的构造函数:
public domecho02()
{
// make a nice border
emptyborder eb = new emptyborder(5,5,5,5);
bevelborder bb = new bevelborder(bevelborder.lowered);
compoundborder cb = new compoundborder(eb,bb);
this.setborder(new compoundborder(cb,eb));
// set up the tree
jtree tree = new jtree();
// build left-side view
jscrollpane treeview = new jscrollpane(tree);
treeview.setpreferredsize(
new dimension( leftwidth, windowheight ));
// build right-side view
jeditorpane htmlpane = new jeditorpane("text/html","");
htmlpane.seteditable(false);
jscrollpane htmlview = new jscrollpane(htmlpane);
htmlview.setpreferredsize(
new dimension( rightwidth, windowheight ));
// build split-pane view
jsplitpane splitpane =
new jsplitpane(jsplitpane.horizontal_split,
treeview, htmlview )
splitpane.setcontinuouslayout( true );
splitpane.setdividerlocation( leftwidth );
splitpane.setpreferredsize(
new dimension( windowwidth + 10, windowheight+10 ));
// add gui components
this.setlayout(new borderlayout());
this.add("center", splitpane );
} // constructor
创建适配器以便在jtree中显示dom
现在已经有一个gui框架来显示jtree ,下一步是获得jtree以显示dom。但是jtree 希望显示treemodel。 dom 是树,但它不是treemodel。所以需要创建一个适配器使得dom
文档对象模型
