查看DOM的结构
本节将使用上节创建的GUI-fied DomEcho 应用程序直观地检查DOM。你将看到哪些节点构成DOM以及它们是怎么安排的。利用现有知识,将能很好地构建并修改文档对象模型的结构。
显示简单树
从显示简单文件开始学习,这样可以理解基本的DOM结构。之后学习引入高级XML元素后产生的结构。
注意:用来创建本节中的图的代码在DomEcho02.java中。显示的文件是slideSample01.xml. (可浏览版本是slideSample01-xml.html.)
图7-1 是在DOM教程中创建的第一个XML文件上运行DomEcho 程序时看到的树。
图 7-1 显示的文档、注释和元素节点
我们还记得每个节点显示的第一位文本是元素type。然后是元素名,如果有的话,然后就是元素值。该视图显示了三个元素类型:Document、Comment和Element。其中只有Document 类型用于整棵树——也就是根节点。注释节点显示了value 属性,而元素节点显示了元素名“slideshow”。
比较图 7-1 和AdapterNode的toString 方法中的代码,看看是否显示了特定节点的名字或值。如果想更清楚些,修改程序以表明正在显示的是哪个属性(例如,N: name,
V: value)。
展开slideshow
元素得到图7-2。
图7-2 展开了元素节点,却没有显示任何属性节点
这里,可以看到分布在Slide(幻灯片)元素之间的Text 节点和Comment 节点。存在空Text 节点,这是因为没有DTD告诉解析器不存在文本。(总的说来,DOM树中的大多数节点是Element 和 Text 节点)
重要!
DOM中,文本节点(text节点)位于元素节点(element节点)之下,并且数据总是保存在文本节点中。可能DOM处理中最常见的错误是浏览元素节点并且希望它包含存储在该节点中的数据。并非如此!即便是最简单的元素节点之下也有一个文本节点。例如,<size>12</size>,它之下有一个元素节点 (size)和一个包含实际数据(12)的文本节点。
很明显,该图中缺少Attribute 节点。对org.w3c.dom.Node 中的表的检查表明实际上有一个属性节点类型。但是它们不是DOM层次结构中的子节点。实际上是通过Node接口的getAttributes 方法得到它们的。
注意:显示文本节点是AdapterNode的toString 方法中引入下面几行代码的一个理由。如果删除它们,将会看到文本中新行字符产生的有趣的字符(通常为方块)。
String t = domNode.getNodeValue().trim();
int x = t.indexOf(");
if (x >= 0) t = t.substring(0, x);
s += t;
显示比较复杂的树
这里,将显示SAX教程结尾处创建的示例XML文件,以查看DOM中实体引用、处理指令和CDATA段是怎样的。
图 7-3 是在slideSample10.xml 上运行DomEcho 应用程序的结果,slideSample10.xml 文件引用了识别文档的DTD的DOCTYPE 项。
图 7-3 显示的DocType节点
DocType 接口实际上是w3c.org.dom.Node的扩展。它定义了getEntities 方法,可以使用该方法来取得Entity 节点——该节点定义了如product 实体这样的实体,它的值为“WonderWidgets”。同Attribute 一样, Entity 节点不是DOM节点的子节点。
展开slideshow 节点后,得到图 7-4。
图 7-4 处理指令节点
这里,处理指令节点被高亮度显示,以证明这些节点确实出现在树中。name属性包含了目标规范,标识了指令指向的应用程序。value 属性包含指令的文本。
注意,这里也显示了空文本节点,虽然DTD规定slideshow 只能包含slide 元素,不能包含文本。从逻辑上说,你可能认为这些节点不应该出现。(但该文件通过SAX解析器时,这些元素产生ignorableWhitespace 事件,而不是character 事件。)
移到第二个slide 元素并且打开它下面的item 元素得到图7-5。
图7-5 JAXP 1.2 DOM ——实体引用返回的Item Text
这里,将包含版权文本的文本节点插入DOM而不是指向的实体引用。
在大多数应用程序中,你都希望插入文本。这样,查看节点下的文本时,不需要担心它可能包含的实体引用。
对于其他应用程序,你可能希望能够重构原来的XML。例如,一个文本编辑器应用程序需要保存用户修改结果,并且在处理中不能抛实体引用。
通过各类DocumentBuilderFactory API可以控制创建的DOM结构。例如,添加下面突出显示的代码,产生DOM结构,如图7-6所示。
public static void main(String argv[])
{
...
DocumentBuilderFactory factory
=
DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(true);
...
图 7-6 1.4平台上的JAXP 1.1——实体引用节点
这里,高亮显示了实体引用节点。注意实体引用包含它之下的多个节点。该例子仅仅显示了注释和文本节点,但是实体能够包含其他元素节点。
最后,移动到最后一个slide下的最后一个item 元素,得到图7-7。
图 7-7 CDATA节点
这里,高亮显示了CDATA节点。注意它下面没有节点。由于完全没有解释CDATA 段,它的所有内容都包含在节点的value 属性中。
词法控件摘要
需要使用词法信息来重建XML文档的原始语法。前面已经讨论了,编辑应用程序必须保护词法信息,你希望在这里保存文档,该文档精确反映了原型——包括完整的注释、实体引用和任何CDATA段,可能在一开始就包含它们了。
然而,绝大多数应用程序仅仅关心XML结构的内容。它们能够忽略注释,并且它们并不关心数据是否在CDATA 段中,如纯文本,或它是否包含实体引用。对于这类应用程序,只需要最少的词法信息,因为它简单化了应用程序必须检查的DOM节点的数目和类型。
下面的DocumentBuilderFactory 方法能够控制DOM中看到的词法信息:
·
setCoalescing()
将CDATA 节点转换为Text节点,并且将它附加到邻接Text节点(如果有的话)。
·
setExpandEntityReferences()
展开实体引用节点
·
setIgnoringComments()
忽略注释
·
setIgnoringElementContentWhitespace()
忽略元素内容中的可忽略空白
这些属性的默认值都是false。 表 7-2 是原始表单中,保留重构原始文档需要的所有词法信息的设置。也显示了构建最简单的DOM的设置,所以应用程序能够关注数据的语义内容,而不用担心词法语义细节。
|
表7-2 配置DocumentBuilderFactory
|
|
API
|
保留词法信息
|
关注内容
|
|
setCoalescing()
|
false
|
true
|
|
setExpandEntityReferences()
|
true
|
false
|
|
setIgnoringComments()
|
false
|
true
|
|
setIgnoringElement
ContentWhitespace()
|
false
|
true
|
完成
这时,你已经看到了DOM树中遇到的所有节点。还有一两个在下一节中会介绍,但是现在你具有了创建或修改DOM结构需要的知识。下一节,你会知道如何将DOM 转换成使用于交互GUI的Jtree。或者,如果你乐意,你可以跳过本节直接跳到DOM教程的第五节,创建和操作DOM,在那里你会知道如何从头开始创建DOM。
从DOM创建用户友好的JTree
既然你已经知道了DOM内部看起来是怎么样的,现在可以修改DOM或从头开始构建一个。在开始之前,本节对JTreeModel 进行了一些修改,能让你产生一个更加友好的适用于GUI的JTree 版本。
压缩树视图
用树的形式显示DOM很适合试验,并更能容易理解DOM的工作原理。但是这不是多数用户希望看到的Jtree里的“友好”的显示。然而,结果表明仅需要进行很少的修改就能将TreeModel 适配器变成更加友好的显示。本节中,你将进行这些修改。
注意:本节讨论的代码在DomEcho03.java中。它运行在文件slideSample01.xml上。(可浏览版本是slideSample01-xml.html.)
使操作可选则
修改适配器时,将压缩DOM的视图,消除你真的想显示的节点外的所有节点。首先定义一个布尔变量,用它来控制是压缩还是展开DOM视图:
public class DomEcho extends JPanel
{
static Document document;
boolean
compress = true;
static final int windowHeight
= 460;
...
识别树节点
下一步是识别要在树中显示的节点。添加下面的代码:
...
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
public class DomEcho extends JPanel
{
...
public static void makeFrame()
{
...
}
// An array of names for DOM
node-type
static final String[] typeName
= {
...
};
static final int ELEMENT_TYPE = Node.ELEMENT_NODE;
//
The list of elements to display in the tree
static
String[] treeElementNames = {
"slideshow",
"slide",
"title", //
For slideshow #1
"slide-title", // For slideshow #10
"item",
};
boolean
treeElement(String elementName) {
for
(int i=0; i<treeElementNames.length; i++) {
if
( elementName.equals(treeElementNames[i]) )
return
true;
}
return
false;
}
通过该代码,建立一个可用来识别ELEMENT 节点类型的常量,声明树中的元素的名字,并创建方法以判断给定元素名是不是一个“树元素”。由于slideSample01.xml有title 元素,并且slideSample10.xml 有slide-title 元素,所以应该建立该数组的内容,这样它就能使用任何一个数据文件。
注意:这里创建的机制基于这样一个事实,如slideshow 和 slide 这样的structure 节点从来不包含文本,而文本将常出现在像item这类content 节点中。虽然这些“content”节点可能包含slideShow10.xml中的子元素。DTD约束这些子元素只能是XHTML节点。由于它们是XHTML节点(HTML的XML版本必须结构良好),可以将item 节点下的整个子结构结合到子字符串中,并且在构成应用程序窗口的另一半的htmlPane 中显示。本节的第二部分中,你将执行该连接,将文本和XHTML作为htmlPane的内容显示。
虽然你能够很容易引用该类中定义的节点类型,org.w3c.dom.Node还是定义了ELEMENT_TYPE 常量,使得代码更具有可读性。DOM中的每个节点都有名字、类型和(潜在的)子节点列表。返回这些值的函数是getNodeName()、getNodeType和getChildNodes()。使用如下代码定义自己的常量:
Node node = nodeList.item(i);
int type = node.getNodeType();
if (type == ELEMENT_TYPE) {
....
作为风格上的选择,其他常量使得读取器(和我们自己) 清楚地知道正在做些什么。这里,很清楚地知道什么时候处理节点对象,什么时候处理类型常量。否则,将很希望编写代码if (node == ELEMENT_NODE),当然它不起什么作用。
控制节点可视性
下一步是修改AdapterNode的childCount 函数,这样它仅仅计算“树元素”节点——指定这些节点可在Jtree中显示。进行下面的修改:
public class DomEcho extends JPanel
{
...
public class AdapterNode
{
...
public AdapterNode child(int
searchIndex) {
...
}
public int childCount() {
if
(!compress) {
//
Indent this
return domNode.getChildNodes().getLength();
}
int
count = 0;
for
(int i=0;
i<domNode.getChildNodes().getLength();
i++)
{
org.w3c.dom.Node
node =
domNode.getChildNodes().item(i);
if
(node.getNodeType() == ELEMENT_TYPE
&&
treeElement( node.getNodeName() ))
{
++count;
}
}
return
count;
}
} // AdapterNode
该代码唯一棘手的是在比较节点之前,检查节点确定该节点是元素节点。DocType 节点使得必须这样做,这是因为它和slideshow元素具有相同的名字“slideshow”。
|