Loading

XML dom

将文件解析为文档

三步过程

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

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

创建 DocumentBuilderFactory。DocumentBuilderFactory 对象创建 DocumentBuilder。
创建 DocumentBuilder。DocumentBuilder 执行实际的解析以创建 Document 对象。
解析文件以创建 Document 对象。
现在您可以开始构建应用程序了。

回页首

基本的应用程序

首先创建一个基本的应用程序,即一个名为 OrderProcessor 的类。

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;

public class OrderProcessor {
   public static void main (String args[]) {
      File docFile = new File("orders.xml");
      Document doc = null;      
      try {

       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       DocumentBuilder db = dbf.newDocumentBuilder();
       doc = db.parse(docFile);

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

首先,Java 代码导入必要的类,然后它创建 OrderProcessor 应用程序。本教程中的例子仅处理一个文件,因此为简洁起见,应用程序包含了对该文件的直接引用。

因此 Document 对象可以在以后使用,应用程序把它定义在 try-catch 块之外。

在 try-catch 块中,应用程序创建了 DocumentBuilderFactory,然后再使用它来创建 DocumentBuilder。 最后,DocumentBuilder 解析文件以创建 Document。

回页首

解析器设置

使用 DocumentBuilder 创建解析器的优点之一在于能够控制 DocumentBuilderFactory 创建的解析器上的各种设置。例如,可以设置解析器验证文档:

...

try {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         
         dbf.setValidating(true);
         
         DocumentBuilder db = dbf.newDocumentBuilder();
         doc = db.parse(docFile);
      } catch (Exception e) {
...

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

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

解析器异常

由于在创建解析器时存在所有这些可能性,其中许多地方都可能会出错。正如这里的例子所表明的,应用程序把所有这些内容转储到一个单一的通用 Exception 中,就调试而言,这样可能不是很有帮助。

为更好地查明问题,您可以捕捉与创建和使用解析器的各方面相关的特定异常:

...

      try {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         DocumentBuilder db = dbf.newDocumentBuilder();
         doc = db.parse(docFile);
 } catch (javax.xml.parsers.ParserConfigurationException pce) {
         System.out.println("The parser was not configured correctly.");
         System.exit(1);
      } catch (java.io.IOException ie) {
         System.out.println("Cannot read input file.");
         System.exit(1);
      } catch (org.xml.sax.SAXException se) {
         System.out.println("Problem parsing the file.");
         System.exit(1);
      } catch (java.lang.IllegalArgumentException ae) {
         System.out.println("Please specify an XML source.");
         System.exit(1);

      }
...

一旦解析器已创建了一个 Document,应用程序就能单步调试它以检查数据。

 

 

单步调试文档

获取根元素

一旦解析了文档并创建了一个 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());

 }
}


编译和运行这个应用程序将输出根元素的名称 orders。

回页首

获取节点的孩子

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

注意这里的文档仅有两个元素,但是 NodeList 包含五个孩子,包括包含换行的三个文本节点 ―― 还要注意节点和元素在 DOM 中不是等价的。其他三个节点是包含换行的文本节点。

...


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 值,而不是预期的文本。包含实际内容作为其值的,是作为元素的孩子的文本节点。

...

 import org.w3c.dom.Node; 

...
      
      //STEP 3:  Step through the children
 for (Node child = root.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
         System.out.println(start.getNodeName()+" = " 
                                       +start.getNodeValue());
      }

   }
}
...


回页首

在多层孩子中递归

使用 getFirstChild() 和 getNextSibling() 中的代码显示了第一层孩子,但那远远不是整个文档。为了看到所有元素,必须将前一个例子中的功能转换为一个方法并递归地调用。

应用程序首先从根元素开始,向屏幕输出名称和值。然后应用程序就像以前一样遍历根元素的每个孩子。但是对于每个孩子,应用程序还会遍历该孩子的每个孩子,即检查根元素的所有孩子和孙子。

...

public class OrderProcessor {
   
 private static void stepThrough (Node start)
   {

      System.out.println(start.getNodeName()+" = "+start.getNodeValue());   

      for (Node child = start.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
 stepThrough(child);

      }
 }


   public static void main (String args[]) {
      File docFile = new File("orders.xml");
      
...     
      System.out.println("There are "+children.getLength()
                            +" nodes in this document.");

      //STEP 4:  Recurse this functionality
 stepThrough(root);

   }
}


回页首

包含属性

到目前为止,所编写的 stepThrough() 方法能够遍历大多数类型的节点,但是它完全地遗漏了属性,因为属性不是任何节点的孩子。为了显示属性,可修改 stepThrough() 来检查元素节点的属性。

下面修改过的代码检查每个节点的输出,通过将节点的 nodeType 与常量 ELEMENT_NODE 作比较,从而确定它是否为一个元素。Node 对象带有成员常量,它们表示每种类型的节点,比如 ELEMENT_NODE 或 ATTRIBUTE_NODE。如果 nodeType 与 ELEMENT_NODE 匹配,它就是一个元素。

对于找到的每个元素,应用程序都会创建一个包含该元素的所有属性的 NamedNodeMap。应用程序能够迭代 NamedNodeMap,输出每个属性的名称和值,就像它迭代 NodeList 一样。

...

 import org.w3c.dom.NamedNodeMap;

...
private static void stepThroughAll (Node start)
   {
      System.out.println(start.getNodeName()+" = "+start.getNodeValue());   
         
 if (start.getNodeType() == start.ELEMENT_NODE) 
      {   
          NamedNodeMap startAttr = start.getAttributes();
          for (int i = 0; 
               i < startAttr.getLength();
               i++) {
             Node attr = startAttr.item(i);
             System.out.println("  Attribute:  "+ attr.getNodeName()
                                          +" = "+attr.getNodeValue());
          }   
      } 

      
      for (Node child = start.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
         stepThroughAll(child);
      }
   }

 

 

 

编辑文档

更改节点的值

检查一下 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());
      }

 }
}


注意应用程序挑选了 status 节点,虽然这些节点是根元素的孙子,而不是直接的孩子。getElementsByTagName() 单步调试文档,并查找具有特定名称的所有元素。

回页首

添加节点:准备数据

与更改现有节点不同,有时添加一个节点是必要的,而您可以通过多种方式来做到这点。在本例中,应用程序汇总每个 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 {
...


需要重点注意的是,如果具有该名称的节点不存在,setAttribute() 就会创建一个属性节点,因此在本例中应用程序可以完全略过 createAttribute()。

回页首

删除属性

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

删除信息是相当简单的,只需使用 removeAttribute() 来删除数据。

...

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

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

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


然而接下来的步骤要用到限额信息,因此要在继续之前删除这个最新的更改。

 

 

 

输出文档

准备数据

到目前为止,本教程已考察了如何接受、使用和操作 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),然后将它发送到结果

 

 

 

 

 

 

 

 

 

 

 

 

 
将文件解析为文档

三步过程

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

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

创建 DocumentBuilderFactory。DocumentBuilderFactory 对象创建 DocumentBuilder。
创建 DocumentBuilder。DocumentBuilder 执行实际的解析以创建 Document 对象。
解析文件以创建 Document 对象。
现在您可以开始构建应用程序了。

回页首

基本的应用程序

首先创建一个基本的应用程序,即一个名为 OrderProcessor 的类。

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;

public class OrderProcessor {
   public static void main (String args[]) {
      File docFile = new File("orders.xml");
      Document doc = null;      
      try {

       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       DocumentBuilder db = dbf.newDocumentBuilder();
       doc = db.parse(docFile);

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

首先,Java 代码导入必要的类,然后它创建 OrderProcessor 应用程序。本教程中的例子仅处理一个文件,因此为简洁起见,应用程序包含了对该文件的直接引用。

因此 Document 对象可以在以后使用,应用程序把它定义在 try-catch 块之外。

在 try-catch 块中,应用程序创建了 DocumentBuilderFactory,然后再使用它来创建 DocumentBuilder。 最后,DocumentBuilder 解析文件以创建 Document。

回页首

解析器设置

使用 DocumentBuilder 创建解析器的优点之一在于能够控制 DocumentBuilderFactory 创建的解析器上的各种设置。例如,可以设置解析器验证文档:

...

try {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         
         dbf.setValidating(true);
         
         DocumentBuilder db = dbf.newDocumentBuilder();
         doc = db.parse(docFile);
      } catch (Exception e) {
...

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

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

解析器异常

由于在创建解析器时存在所有这些可能性,其中许多地方都可能会出错。正如这里的例子所表明的,应用程序把所有这些内容转储到一个单一的通用 Exception 中,就调试而言,这样可能不是很有帮助。

为更好地查明问题,您可以捕捉与创建和使用解析器的各方面相关的特定异常:

...

      try {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         DocumentBuilder db = dbf.newDocumentBuilder();
         doc = db.parse(docFile);
 } catch (javax.xml.parsers.ParserConfigurationException pce) {
         System.out.println("The parser was not configured correctly.");
         System.exit(1);
      } catch (java.io.IOException ie) {
         System.out.println("Cannot read input file.");
         System.exit(1);
      } catch (org.xml.sax.SAXException se) {
         System.out.println("Problem parsing the file.");
         System.exit(1);
      } catch (java.lang.IllegalArgumentException ae) {
         System.out.println("Please specify an XML source.");
         System.exit(1);

      }
...

一旦解析器已创建了一个 Document,应用程序就能单步调试它以检查数据。

 

 

单步调试文档

获取根元素

一旦解析了文档并创建了一个 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());

 }
}


编译和运行这个应用程序将输出根元素的名称 orders。

回页首

获取节点的孩子

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

注意这里的文档仅有两个元素,但是 NodeList 包含五个孩子,包括包含换行的三个文本节点 ―― 还要注意节点和元素在 DOM 中不是等价的。其他三个节点是包含换行的文本节点。

...


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 值,而不是预期的文本。包含实际内容作为其值的,是作为元素的孩子的文本节点。

...

 import org.w3c.dom.Node; 

...
      
      //STEP 3:  Step through the children
 for (Node child = root.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
         System.out.println(start.getNodeName()+" = " 
                                       +start.getNodeValue());
      }

   }
}
...


回页首

在多层孩子中递归

使用 getFirstChild() 和 getNextSibling() 中的代码显示了第一层孩子,但那远远不是整个文档。为了看到所有元素,必须将前一个例子中的功能转换为一个方法并递归地调用。

应用程序首先从根元素开始,向屏幕输出名称和值。然后应用程序就像以前一样遍历根元素的每个孩子。但是对于每个孩子,应用程序还会遍历该孩子的每个孩子,即检查根元素的所有孩子和孙子。

...

public class OrderProcessor {
   
 private static void stepThrough (Node start)
   {

      System.out.println(start.getNodeName()+" = "+start.getNodeValue());   

      for (Node child = start.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
 stepThrough(child);

      }
 }


   public static void main (String args[]) {
      File docFile = new File("orders.xml");
      
...     
      System.out.println("There are "+children.getLength()
                            +" nodes in this document.");

      //STEP 4:  Recurse this functionality
 stepThrough(root);

   }
}


回页首

包含属性

到目前为止,所编写的 stepThrough() 方法能够遍历大多数类型的节点,但是它完全地遗漏了属性,因为属性不是任何节点的孩子。为了显示属性,可修改 stepThrough() 来检查元素节点的属性。

下面修改过的代码检查每个节点的输出,通过将节点的 nodeType 与常量 ELEMENT_NODE 作比较,从而确定它是否为一个元素。Node 对象带有成员常量,它们表示每种类型的节点,比如 ELEMENT_NODE 或 ATTRIBUTE_NODE。如果 nodeType 与 ELEMENT_NODE 匹配,它就是一个元素。

对于找到的每个元素,应用程序都会创建一个包含该元素的所有属性的 NamedNodeMap。应用程序能够迭代 NamedNodeMap,输出每个属性的名称和值,就像它迭代 NodeList 一样。

...

 import org.w3c.dom.NamedNodeMap;

...
private static void stepThroughAll (Node start)
   {
      System.out.println(start.getNodeName()+" = "+start.getNodeValue());   
         
 if (start.getNodeType() == start.ELEMENT_NODE) 
      {   
          NamedNodeMap startAttr = start.getAttributes();
          for (int i = 0; 
               i < startAttr.getLength();
               i++) {
             Node attr = startAttr.item(i);
             System.out.println("  Attribute:  "+ attr.getNodeName()
                                          +" = "+attr.getNodeValue());
          }   
      } 

      
      for (Node child = start.getFirstChild(); 
          child != null;
          child = child.getNextSibling())
      {
         stepThroughAll(child);
      }
   }

 

 

 

编辑文档

更改节点的值

检查一下 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());
      }

 }
}


注意应用程序挑选了 status 节点,虽然这些节点是根元素的孙子,而不是直接的孩子。getElementsByTagName() 单步调试文档,并查找具有特定名称的所有元素。

回页首

添加节点:准备数据

与更改现有节点不同,有时添加一个节点是必要的,而您可以通过多种方式来做到这点。在本例中,应用程序汇总每个 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 {
...


需要重点注意的是,如果具有该名称的节点不存在,setAttribute() 就会创建一个属性节点,因此在本例中应用程序可以完全略过 createAttribute()。

回页首

删除属性

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

删除信息是相当简单的,只需使用 removeAttribute() 来删除数据。

...

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

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

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


然而接下来的步骤要用到限额信息,因此要在继续之前删除这个最新的更改。

 

 

 

输出文档

准备数据

到目前为止,本教程已考察了如何接受、使用和操作 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),然后将它发送到结果

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2013-10-27 10:45  asashadow  阅读(264)  评论(0)    收藏  举报