学习笔记-DOM知识点

以下资料均来自于[url]www.ibm.com.cn[/url]
1、将文件解析为文档

三步过程

为了使用 XML 文件中的信息,必须解析文件以创建一个 Document 对象。

Document 对象是一个接口,因而不能直接将它实例化;一般情况下,应用程序会相应使用一个工厂。准确的过程因实现而异,但是基本思想是相同的。(同样,Level 3 标准化了这个任务)。在这个例子 Java 环境中,解析文件是一个三步过程:

1. 创建 DocumentBuilderFactory。DocumentBuilderFactory 对象创建 DocumentBuilder。
2. 创建 DocumentBuilder。DocumentBuilder 执行实际的解析以创建 Document 对象。
3. 解析文件以创建 Document 对象。

现在您可以开始构建应用程序了。
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(true);
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(docFile);

Java 的 DOM Level 2 实现允许通过以下方法控制解析器的参数:

* setCoalescing():决定解析器是否要将 CDATA 节点转换为文本,以及是否要和周围的文本节点合并(如果适用的话)。其默认值为 false。
* setExpandEntityReferences():确定是否要展开外部实体引用。如果为 true,外部数据将插入文档。其默认值为 true。(请参阅 参考资料 以了解关于使用外部实体的技巧)。
* setIgnoringComments():确定是否要忽略文件中的注释。其默认值为 false。
* setIgnoringElementContentWhitespace():确定是否要忽略元素内容中的空白(类似于浏览器对待 HTML 的方式)。其默认值为 false。
* setNamespaceAware():确定解析器是否要注意名称空间信息。其默认值为 false。
* setValidating():默认情况下,解析器不验证文档。将这个参数设置为 true 可打开验证功能。
2、获取根元素

一旦解析了文档并创建了一个 Document,应用程序就能单步调试该结构以审核、查找或显示信息。这种导航功能是将要在 Document 上执行的许多操作的基础。

对文档的单步调试首先从根元素开始。格式良好的文档仅有一个根元素,也称为 DocumentElement。应用程序首先检索这个元素。
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;

import org.w3c.dom.Element;

public class OrderProcessor {
...
System.exit(1);
}

//STEP 1: Get the root element

Element root = doc.getDocumentElement();
System.out.println("The root element is " + root.getNodeName());

}
}

3、获取节点的孩子

一旦应用程序确定了根元素,它就把根元素的孩子的列表作为一个 NodeList 来检索。NodeList 类是一系列的项,应用程序将逐个迭代这些项。 在本例中,为简洁起见,应用程序通过仅显示有多少元素出现在生成的 NodeList 中,从而获取孩子节点和验证检索结果。
import org.w3c.dom.NodeList;

...
//STEP 1: Get the root element
Element root = doc.getDocumentElement();
System.out.println("The root element is "+root.getNodeName());

//STEP 2: Get the children
NodeList children = root.getChildNodes();
System.out.println("There are "+children.getLength()
+" nodes in this document.");

}
}

使用 getFirstChild() 和 getNextSibling()

父子和兄弟关系提供了迭代某个节点的所有孩子的替代方法,它在某些场合下可能更为适宜,比如在这些关系和孩子的出现顺序对理解数据至关重要的时候。

在 Step 3 中,for 循环首先从根元素的第一个孩子开始。应用程序迭代第一个孩子的所有兄弟,直至已全部对它们求值。

每次应用程序执行该循环,它都要检索一个 Node 对象,输出其名称和值。注意 orders 的五个孩子包括 orders 元素和三个文本节点。还要注意元素具有一个 null 值,而不是预期的文本。包含实际内容作为其值的,是作为元素的孩子的文本节点。
for (Node child = root.getFirstChild(); 
child != null;
child = child.getNextSibling())
{
System.out.println(start.getNodeName()+" = "
+start.getNodeValue());
}

}
}


4、编辑文档

更改节点的值

检查一下 XML 文档内容的常量是有用的,但是在处理全功能的应用程序时,您可能需要更改数据以添加、编辑、移动或删除信息。编辑数据的能力对创建新 XML 文档的过程也是至关重要的。这类更改中最简单的情况就是更改元素的文本内容。

这里的目标是更改某个元素的文本节点的值,在此例中是将每个 order 的 status 设置为 “processed”,然后向屏幕输出新的值。

使用起始节点(root)以及要更改的元素名称和要更改到的目标值作为参数,调用 changeOrder() 方法。

changeOrder() 首先检查节点的名称,以确定它是否为要编辑的元素之一。如果是,应用程序需要更改的不是这个节点的值,而是这个节点的第一个孩子的值,因为这第一个孩子才是实际包含内容的文本节点。


在任一种情况下,应用程序都要检查每个孩子,就像它在第一次单步调试文档时一样。

当完成更改时,更改后的值将使用 getElementsByTagName() 来检查。这个方法返回具有指定名称(比如 status)的所有孩子的列表。然后应用程序就能够检查该列表中的值,以检验 changeOrder() 方法调用的有效性。
public class OrderProcessor {

private static void changeOrder (Node start,
String elemName,
String elemValue)
{
if (start.getNodeName().equals(elemName)) {
start.getFirstChild().setNodeValue(elemValue);
}

for (Node child = start.getFirstChild();
child != null;
child = child.getNextSibling())
{
changeOrder(child, elemName, elemValue);
}
}

...
public static void main (String args[]) {
...

// Change text content

changeOrder(root, "status", "processing");
NodeList orders = root.getElementsByTagName("status");
for (int orderNum = 0;
orderNum < orders.getLength();
orderNum++)
{
System.out.println(orders.item(orderNum)
.getFirstChild().getNodeValue());
}

}
}

添加节点:准备数据

与更改现有节点不同,有时添加一个节点是必要的,而您可以通过多种方式来做到这点。在本例中,应用程序汇总每个 order 的价格,然后添加一个 total 元素。它通过接受每个订单,循环迭代它的每件商品以获取商品价格,然后汇总这些价格,从而获得总价格。然后应用程序向订单添加一个新的元素(参见下面的代码清单)。

首先,应用程序检索 order 元素,就像它检索 status 元素一样。然后它循环迭代这其中的每个元素。

对于这其中的每个 order,应用程序都需要其 item 元素的一个 NodeList,因此应用程序必须首先将 order Node 强制转换为 Element,才能使用 getElementsByTagName()。

然后应用程序可以循环迭代 item 元素,以寻找选定的 order。应用程序将每个 item 强制转换为 Element,以便能够根据名称检索 price 和 qty。它是通过使用 getElementsByTagName() 来实现的。因为每件商品只有一个名称,它可以直接转到 item(0),即生成的 NodeList 中的第一个条目。这第一个条目表示 price(或 qty)元素。它就是从那里获得文本节点的值的。

文本节点的值是 String 值,应用程序然后会将它转换为一个 double 值,以允许进行必要的数学运算。

当应用程序检查完每个订单的每件商品时,total 就是一个代表总价格的 double 值。然后 total 被转换为 String 值,以便能将它用作新元素的内容,<total> 最终加入了 order。
changeOrder(root, "status", "processing");
NodeList orders = root.getElementsByTagName(" order ");
for (int orderNum = 0;
orderNum < orders.getLength();
orderNum++)
{

Element thisOrder = (Element)orders.item(orderNum);
NodeList orderItems = thisOrder.getElementsByTagName("item");
double total = 0;
for (int itemNum = 0;
itemNum < orderItems.getLength();
itemNum++) {

// Total up cost for each item and
// add to the order total

//Get this item as an Element
Element thisOrderItem = (Element)orderItems.item(itemNum);

//Get pricing information for this Item
String thisPrice =
thisOrderItem.getElementsByTagName("price").item(0)
.getFirstChild().getNodeValue();
double thisPriceDbl = new Double(thisPrice).doubleValue();

//Get quantity information for this Item
String thisQty =
thisOrderItem.getElementsByTagName("qty").item(0)
.getFirstChild().getNodeValue();
double thisQtyDbl = new Double(thisQty).doubleValue();

double thisItemTotal = thisPriceDbl*thisQtyDbl;
total = total + thisItemTotal;
}
String totalString = new Double(total).toString();

}

添加节点:向文档添加节点

您可以用许多方法创建新 Node,而本例将使用其中的几种方法。首先,Document 对象能够创建新的值为 totalString 的文本节点。新的 Node 现在已经存在了,但是还没在任何地方实际连接到 Document。新的 total 元素是以类似的方式创建的,起初也没有实质性的内容。

添加节点的另一种方法是使用 appendChild(),就像这里将节点添加到新的 total 元素一样。

最后,应用程序可以使用 insertBefore() 来向 Document 添加新的元素,同时指定新的 Node,然后指定新 Node 之后的那个 Node。

单步调试文档将检验这些更改。
changeOrder(root, "status", "processing");
NodeList orders = root.getElementsByTagName("order");
for (int orderNum = 0;
orderNum < orders.getLength();
orderNum++)
{
...
String totalString = new Double(total).toString();

Node totalNode = doc.createTextNode(totalString);

Element totalElement = doc.createElement("total");
totalElement.appendChild(totalNode);

thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());

}

stepThrough(root);

删除节点

应用程序不是替换元素的文本,而是将它完全删除。在这个例子中,应用程序检查商品是否有现货。如果没有,它将从订单中删除该商品,而不是将其添加到汇总中。

在将商品成本添加到价款汇总中之前,应用程序会检查 instock 属性的值。如果该值为 N,则不是将它添加到价款汇总中,而是将它完全删除。为此,它使用了 removeChild() 方法,不过首先要使用 getParentNode() 来确定 orderItem 的父节点。实际的 Node 已从文档中删除,但是该方法还是将它作为一个对象返回,以便能根据需要移动它。
//Get this item as an Element
Element thisOrderItem = (Element)orderItems.item(itemNum);

if (thisOrderItem.getAttributeNode("instock")
.getNodeValue().equals("N")) {

Node deadNode =
thisOrderItem.getParentNode().removeChild(thisOrderItem);

} else {

//Get pricing information for this Item
String thisPrice =
thisOrderItem.getElementsByTagName("price").item(0)
.getFirstChild().getNodeValue();
...
total = total + thisItemTotal;

}

}
String totalString = new Double(total).toString();

替换节点

当然,因为某件商品订货不足(backordered)而删除该商品是没有多大意义的。相反,应用程序会使用一个 backordered 项来替换它。

应用程序并不使用 removeChild(),而是简单地使用了replaceChild()。注意在此示例中,该方法还会返回旧的节点,以便能根据需要将它移往别处,或许可以移动到一个新 Document,它列出了所有订单不足的商品。

注意由于没有内容被添加到该元素,因此它是一个空元素。空元素没有内容,并且可以用一种特殊的简写来表示:

<backordered />

有了斜线(/),就不再需要结束标签(</backordered>)。
if (thisOrderItem.getAttributeNode("instock")
.getNodeValue().equals("N")) {

Element backElement = doc.createElement("backordered");

Node deadNode = thisOrderItem.getParentNode()
. replaceChild ( backElement, thisOrderItem);


} else {

创建和设置属性

那么,如果没有标志表明一个 backordered 元素是什么商品,那该怎么正确处理它呢?纠正信息缺乏的方法之一是向元素添加属性。

应用程序首先创建一个 itemid 属性。接下来,它根据原先的 item 元素确定 itemid 的值,然后再自己设置该属性的值。最后,它把该元素添加到文档,就像以前一样。
if (thisOrderItem.getAttributeNode("instock")
.getNodeValue().equals("N")) {

Element backElement = doc.createElement("backordered");

backElement.setAttributeNode(doc.createAttribute("itemid"));

String itemIdString =
thisOrderItem.getAttributeNode("itemid").getNodeValue();
backElement.setAttribute("itemid", itemIdString);


Node deadNode = thisOrderItem.getParentNode().replaceChild(backElement,
thisOrderItem);

} else {

删除属性

应用程序还可以删除属性。例如,在输出中显示客户信用限额信息也许是不可取的,因此应用程序可以临时地将该属性从文档中删除。

删除信息是相当简单的,只需使用 removeAttribute() 来删除数据。
Element thisOrder = (Element)orders.item(orderNum);

Element customer =
(Element)thisOrder.getElementsByTagName("customerid")
.item(0);
customer.removeAttribute("limit");

NodeList orderItems = thisOrder.getElementsByTagName("item");

5、
输出文档

准备数据

到目前为止,本教程已考察了如何接受、使用和操作 XML 数据。要完成这个周期,您还必须能够输出 XML。

对于本教程中的情况,目标输出是一个文件,该文件简单地列出每个订单、依据客户信用限额来确定的订单处理情况,以及 customerid。

<?xml version="1.0" encoding="UTF-8"?>
<processedOrders>
<order>
<status>PROCESSED</status>
<customerid>2341</customerid>
<amount>874.00</amount>
</order>
<order>
<status>REJECTED</status>
<customerid>251222</customerid>
<amount>200.00</amount>
</order>
</processedOrders>


应用程序首先创建要输出的 Document 对象。为方便起见,可以使用创建原先的 Document 的 DocumentBuilder 来创建新的 Document 对象。

...

public static void main (String args[]) {
File docFile = new File("orders.xml");
Document doc = null;
Document newdoc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(docFile);

newdoc = db.newDocument();

} catch (Exception e) {
System.out.print("Problem parsing the file: "+e.getMessage());
}
...
thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());
}


Element newRoot = newdoc.createElement("processedOrders");

NodeList processOrders = doc.getElementsByTagName("order");
for (int orderNum = 0;
orderNum < processOrders.getLength();
orderNum++) {

Element thisOrder = (Element)processOrders.item(orderNum);

Element customerid =
(Element)thisOrder.getElementsByTagName("customerid")
.item(0);
String limit = customerid.getAttributeNode("limit").getNodeValue();

String total = thisOrder.getElementsByTagName("total").item(0)
.getFirstChild().getNodeValue();

double limitDbl = new Double(limit).doubleValue();
double totalDbl = new Double(total).doubleValue();

Element newOrder = newdoc.createElement("order");

Element newStatus = newdoc.createElement("status");
if (totalDbl > limitDbl) {
newStatus.appendChild(newdoc.createTextNode("REJECTED"));
} else {
newStatus.appendChild(newdoc.createTextNode("PROCESSED"));
}

Element newCustomer = newdoc.createElement("customerid");
String oldCustomer = customerid.getFirstChild().getNodeValue();
newCustomer.appendChild(newdoc.createTextNode(oldCustomer));

Element newTotal = newdoc.createElement("total");
newTotal.appendChild(newdoc.createTextNode(total));

newOrder.appendChild(newStatus);
newOrder.appendChild(newCustomer);
newOrder.appendChild(newTotal);

newRoot.appendChild(newOrder);
}

newdoc.appendChild(newRoot);

System.out.print(newRoot.toString());

...


在处理 orders.xml 之后,应用程序创建了一个新的元素 processedOrders,这个新的元素最终将成为新文档的根元素。然后它遍历每个订单。对于每个订单,它都提取其 total 和 limit 信息。

接下来,应用程序为订单创建新元素:order、status、customerid 和 amount。它根据汇总款项是否超过客户的信用限额来填充 status,然后相应地填充其他元素。

一旦应用程序为订单创建了元素,它就必须将这些元素整合起来。它首先向新的 order 元素添加状态、客户信息和汇总款项。然后它把新的 order 添加到 newRoot 元素。

尽管如此,newRoot 元素并没有实际连接到某个父节点。当应用程序完成所有订单的处理时,newRoot 就被追加到新的文档。

最后,应用程序将 newRoot 转换为一个 String,并简单地将它发送到 System.out,从而输出数据。

标准化文档

注意:在节点上,一些版本的 Java 代码不支持这个版本的 toString()。如果出现这种情况,使用 恒等转换 中展示的技巧就可以查看节点的内容。


创建 XML 文件

现在应用程序已经创建了新的信息,将这个信息输出到某个文件是很简单的。

对数据也使用了相同的逻辑,只不过应用程序不是将它输出到屏幕,而是将它输出到一个文件。

要注意的一件重要事情是,由于 XML 数据是文本,因此可以通过任何方式来对它进行格式化。例如,您可以创建 stepThroughAll() 的变体,它将创建缩进的或完美输出的版本。记住,这将创建额外的空白(文本)节点。

...

import java.io.FileWriter;
...

try
{
File newFile = new File("processedOrders.xml");
FileWriter newFileStream = new FileWriter(newFile);

newFileStream.write ("<?xml version=\"1.0\"?>");

newFileStream.write ("<!DOCTYPE
"+doc.getDoctype().getName()+" ");

if (doc.getDoctype().getSystemId() != null) {

newFileStream.write (" SYSTEM ");

newFileStream.write (doc.getDoctype().getSystemId());
}
if (doc.getDoctype().getPublicId() != null) {

newFileStream.write (" PUBLIC ");

newFileStream.write (doc.getDoctype().getPublicId());
}

newFileStream.write (">");

newFileStream.write (newRoot.toString());

newFileStream.close();

} catch (IOException e) {
System.out.println("Can't write new file.");
}
...


恒等转换

对于像本教程中这样简单 Document,很容易设想输出 XML 也是简单的,但是需要考虑许多可能导致问题复杂化的因素 ―― 比如像下面这样很少出现的情况:文件的内容要受到某个 DTD 或模式的影响。通常,最好使用考虑了所有这些可能性的应用程序。

开发人员经常选择用于序列化 Document 的一种方法就是创建一个恒等转换。这是一个不包括样式表的 XSL 转换。例如:

...

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileOutputStream;
...
newdoc.appendChild(newRoot);

try {
DOMSource source = new DOMSource(newdoc);
StreamResult result =
new StreamResult(new FileOutputStream("processed.xml"));

TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();

transformer.transform(source, result);
} catch (Exception e){
e.printStackTrace();
}
}
}


这里创建了一个源和一个结果,但是由于是在使用恒等转换,因此没有创建一个对象来代表样式表。如果这是实际的转换,则可以在 Transformer 的创建过程中使用样式表。否则,Transformer 将简单地接受源(Document),然后将它发送到结果(processed.xml 文件)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值