虽然大概已经了解了DOM,不过对我来说org.w3c.dom的奇怪扫描方式让我对其易使用性大有疑惑,所以还是必须要学习一下DOM。
  这篇文章是在学习DOM的过程中整理出来的资料:
  DOM 文档是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档并且构造层次结构,然后才能做其他事情。由于它是基于信息层次的,因而 DOM 被认为是基于树或基于对象的。
  XML 文件的基本组成部分包括:                 
-                          XML 声明:基本的声明  将这个文件定义为 XML 文档。在声明中指定一种字符编码的情况并不鲜见,如下所示。通过这种方式,不管该 XML 文件使用的语言或字符编码是什么,只要解析器理解特定的编码,它就能够正确地读取该 XML 文件。
 -                           DOCTYPE 声明:XML 是人机之间交换信息的便利手段,但是要使它能够顺利地工作,必须要有一个公共的词汇表。可选的 DOCTYPE 声明可用于指定一个应该用来与此文件做比较的文档(在本例中为  orders.dtd),以确保不会产生任何混淆或丢失信息(例如,丢失一个 userid  或错误拼写某个元素名称)。以这种方式处理过的文档称为有效的文档。成功的有效性检查并不是 XML 所必需的,后面的例子实际上从文档中省略了 DOCTYPE 声明。
 -                           数据本身:XML 文档中的数据必须包含在单个根元素内,比如下面的 orders 元素。要使 XML 文档得到处理,它必须是格式良好的(well-formed)。
 
<?xml version="1.0" encoding="UTF-8"?>
         <!DOCTYPE ORDERS SYSTEM "orders.dtd">
<orders>
<order>
<customerid limit="1000">12341</customerid>
<status>pending</status>
<item instock="Y" itemid="SA15">
<name>Silver Show Saddle, 16 inch</name>
<price>825.00</price>
<qty>1</qty>
</item>
<item instock="N" itemid="C49">
<name>Premium Cinch</name>
<price>49.00</price>
<qty>1</qty>
</item>
</order>
<order>
<customerid limit="150">251222</customerid>
<status>pending</status>
<item instock="Y" itemid="WB78">
<name>Winter Blanket (78 inch)</name>
<price>20</price>
<qty>10</qty>
</item>
</order>
</orders>
  DOM 本质上是节点的集合。 由于一个文档中可能包含不同类型的信息,因此要定义不同类型的节点。  
基本的节点类型:文档、原始、属性和文本
                                                   XML 中最常见的节点类型包括:
                 -                          元素:元素是 XML 的基本构造模块。通常,元素拥有子元素、文本节点,或两者的组合。元素节点也是能够拥有属性的唯一节点类型。
 -                           属性:属性节点包含关于元素节点的信息,但是并不实际认为是元素的孩子,比如在下面的例子中:
 <customerid limit="1000">12341</customerid>
           -                          文本:文本节点就是名副其实的文本。它可以由更多信息组成,也可以只包含空白。
 -                          文档:文档节点是文档中其他所有节点的父亲。 
 
-----------------------------------------
接下来是处理XML文档的说明了:
将文件解析为document
                                                   
                                                     为了使用 XML 文件中的信息,必须解析文件以创建一个                         Document 对象。       
                                        Document 对象是一个接口,因而不能直接将它实例化;一般情况下,应用程序会相应使用一个工厂。准确的过程因实现而异,但是基本思想是相同的。(同样,Level 3 标准化了这个任务。)在这个例子 Java 环境中,解析文件是一个三步过程:
                 -                          创建 DocumentBuilderFactory。  DocumentBuilderFactory 对象创建 DocumentBuilder。
 -                          创建 DocumentBuilder。                         DocumentBuilder 执行实际的解析以创建 Document 对象。
 -                          解析文件以创建 Document 对象。
 
                 现在您可以开始构建应用程序了。
一个例子:
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());
}
}
}
  这里应用程序创建了 DocumentBuilderFactory,然后再使用它来创建 DocumentBuilder。 最后,DocumentBuilder 解析文件以创建 Document。
解析器设置:
                                                     使用 DocumentBuilder 创建解析器的优点之一在于能够控制 DocumentBuilderFactory 创建的解析器上的各种设置。例如,可以设置解析器验证文档:
Java 的 DOM Level 2 实现允许通过以下方法控制解析器的参数:
                 -                                                       setCoalescing():决定解析器是否要将 CDATA 节点转换为文本,以及是否要和周围的文本节点合并(如果适用的话)。其默认值为 false。
 -                                                       setExpandEntityReferences():  确定是否要展开外部实体引用。如果为 true,外部数据将插入文档。其默认值为 true                         。(请参阅参考资料以了解关于使用外部实体的技巧。)
 -                                                       setIgnoringComments():确定是否要忽略文件中的注释。其默认值为 false。
 -                                                       setIgnoringElementContentWhitespace():确定是否要忽略元素内容中的空白(类似于浏览器对待 HTML 的方式)。其默认值为 false。
 -                                                       setNamespaceAware():确定解析器是否要注意名称空间信息。其默认值为 false。
 -                                                       setValidating():默认情况下,解析器不验证文档。将这个参数设置为 true 可打开验证功能。
 
单步调试文档:
                                                     获取根元素:
                                                     一旦解析了文档并创建了一个 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());
 //STEP 2:  Get the children
    NodeList children = root.getChildNodes();
 System.out.println("There are "+children.getLength()
                           +" nodes in this document.");
//STEP 3:  Step through the children
      for (Node child = root.getFirstChild();
   child != null;
   child = child.getNextSibling())
{
  System.out.println(start.getNodeName()+" = "
                                +start.getNodeValue());
}
}
}
                                  获取节点的子节点:
                                                     一旦应用程序确定了根元素,它就把根元素的子节点的列表作为一个 NodeList 来检索。NodeList 类是一系列的项,应用程序将逐个迭代这些项。 在本例中,为简洁起见,应用程序通过仅显示有多少元素出现在结果 NodeList中,从而获取子节点和验证检索结果。 
                                    注意这里的文档仅有两个元素,但是 NodeList 包含五个子节点,包括包含换行的三个文本节点 ―― 还要注意节点和元素在 DOM 中不是等价的。
使用 getFirstChild() 和 getNextSibling():
                                                     父子和兄弟关系提供了迭代某个节点的所有孩子的替代方法,它在某些场合下可能更为适宜,比如在这些关系和孩子的出现顺序对理解数据至关重要的时候。
                   在 Step 3 中,for 循环首先从根元素的第一个子节点开始。 应用程序迭代第一个孩子的所有兄弟,直至已全部对它们求值。
                   每次应用程序执行该循环,它都要检索一个 Node 对象,输出其名称和值。注意 orders 的五个孩子包括 order 元素和三个文本节点。还要注意元素具有一个 null 值,而不是预期的文本。包含实际内容作为其值的,是作为元素的孩子的文本节点。
  还可以通过使用递归来获取所有节点.此外,需要注意的是,在dom中,属性不是任何节点的子节点。用w3c school提供的图示可以理解:

  当你实际使用dom扫描器去获取document的时候,你会发现它会将element之间的空白也作为一个节点包存在文档树中,本来,按照ibm这篇文章的说法是只要设置了setIgnoringElementContentWhitespace(true)就可以让扫描器无视掉这些空白,但是却没有提到,要让这个功能正常工作还必须要有定义过的dtd文件,同理,Node.normalized()方法也是一样的。关于文档标准化的内容,可以读读这一段:
-----------------------------------------
文档标准化
        在 DOM Level 3 中定义的一个新方法是          Document                          接口的          normalizeDocument             方法。从方法名可以看出,您可以使用这个方法来标准化文档。在默认情况下,这个方法完成以下工作:       
       - 标准化            Text               节点,将相邻的            Text                                节点整合为一个            Text               节点。         
 - 根据            EntityReference               节点所引用的实体更新它们的内容。         
 - 验证和修复文档中的命名空间信息,使命名空间格式是格式良好的。
 
       一定要注意,在这个方法中使用的命名空间标准化算法(在 Appendix B 中定义)只对         namespace-aware          节点起作用,这些节点是使用具有“NS”后缀的方法(例如          createElementNS )创建的。         Namespace unaware 节点,即用 DOM Level 1 方法(如          createElement )创建的节点,与所有依赖于 XML 命名空间的处理不完全兼容。如果在文档中有 DOM Level 1 节点,那么在尝试进行命名空间标准化时,          normalizeDocument                      会失败并报告一个错误。一般来说,如果希望使用 XML 命名空间,并且要对文档执行任何需要 XML 命名空间支持的操作,那么就不应该用 DOM Level 1 方法创建节点。对于其他的操作,例如针对 XML Schema 重新验证内存中的文档,也是如此。       
       还可以通过          DOMConfiguration  配置          normalizeDocument ,以便对文档执行其他操作。例如,可以使用这个方法去掉注释,将          CDATASection             节点转换为          Text             节点,或者放弃树中所有命名空间声明属性。还可以通过它一次完成上述所有工作,从而轻松地使文档具有可以自然地映射到 XML Infoset 的形式。下面的程序片段展示了如何用          Document.config                    控制           normalizeDocument 。 
使用 Document.config 控制 normalizeDocument           
     
// retrieve document configuration
DOMConfiguration config = document.getConfig();
// remove comments from
config.setParameter("comments", false);
// remove namespace declarations
config.setParameter("namespace-declarations", false);
// transform document
core.normalizeDocument();
// put document into a form closest to the XML Infoset
config.setParameter("infoset", true);
// transform document
core.normalizeDocument();
normalizeDocument             方法还允许您用 XML Schema 或者 DTD 对内存中的文档进行重新验证。过去,要在文档修改后对它进行重新验证,必须将它保存为一个文件,再用验证解析器读回去。有了这种新方法,现在就可以通过让 DOM 实现重新验证内存中的文档从而更有效地完成这项工作。为此,首先需要将          DOMConfiguration             的          validate             参数设置为          true 。然后需要实现一个          DOMErrorHandler             对象,验证错误将报告给这个对象,再用          error-handler             参数将这个对象注册到           Document                    上。这与对 SAX 解析器所做的工作很类似。最后,可以通过调用          normalizeDocument             检查文档是否有效。
目前,还没有访问 XML Schema Post-Schema Validation Infoset(PSVI)的标准 API。不过,DOM Level 3 允许您获取一些 PSVI 信息。例如,如果希望获得 PSVI 标准化的模式值属性,那么就将          DOMConfiguration           上的          datatype-normalization             和          validate           参数设为“true”,并用经过 XML Schema 标准化的值调用          normalizeDocument                      以更新树 ——这意味着文档中属性值和元素内容现在表示的是 PSVI 标准化的模式值属性。       
-----------------------------------------
添加节点:
  一般要先构造一个element 并且要把它的值作为子节点append到这个element下。
删除节点:
  直接把要删除的节点remove就可以,子节点会跟着消除
最后就是写入XML文档了。
  如果用递归的方法去遍历文档树是个很不实用的方法。所以看这篇文章:http://www.ibm.com/developerworks/cn/java/l-javaxml/
  在JAXP中所提供的标准的更新原始XML文档的方法就是调用XSLT引擎,亦即使用TransformerFactory和Transformer类。请看下面的Java代码片断:
    
 //首先创建一个DOMSource对象,该构造函数的参数可以是一个Document对象doc代表更改后的DOM Tree。
 DOMSource doms = new DOMSource (doc);
 
 //创建一个File对象,代表DOM Tree所包含的数据的输出介质,这是一个XML文件。
 File f = new File ("XMLOutput.xml");
 //创建一个StreamResult对象,该构造函数的参数可以取为File对象。
 StreamResult sr = new StreamResult (f);
 //下面调用JAXP中的XSLT引擎来实现输出DOM Tree中的数据到XML文件中的功能。
 //XSLT引擎的输入为DOMSource对象,输出为StreamResut对象。
 try
 {
               //首先创建一个TransformerFactory对象,再由此创建Transformer对象。Transformer类相当于一个XSLT引擎。通常我们使用它来处理XSL文件,但是在这里我们使用它来输出XML文档。
  TransformerFactory tf=TransformerFactory.newInstance(); 
  Transformer t=tf.newTransformer ();
  //关键的一步, 调用Transformer对象 (XSLT引擎)的transform()方法,该方法的第一个参数是DOMSource对象,第二个参数是StreamResult对象。
  t.transform(doms,sr); 
 }
 catch (TransformerConfigurationException tce)
 {   
  System.out.println("Transformer Configuration Exception\n-----");
  tce.printStackTrace();  
 }
 catch (TransformerException te)
 {   
  System.out.println ("Transformer Exception\n---------");   
  te.printStackTrace ();  
 }   
 
     在实际的应用中,我们可以应用传统的DOM API从XML文档中获取DOM Tree,然后根据实际的需求对DOM Tree执行各种操作,得到最终的Document对象,接下来可以由此Document对象创建DOMSource对象,剩下的事情就是照搬上面的代码 了,程序运行完毕后, XMLOutput.xml就是你所需要的结果(当然了,你可以随意更改StreamResult类构造函数的参数,指定不同的输出介质,而不必是千篇一 律的XML文档)。
      这个方法最大的好处在于可以随心所欲的控制DOM Tree中的内容输出到输出介质中的格式,但是光靠TransformerFactory类和Transformer类并不能实现这个功能,还需要依赖OutputKeys类的帮助。 完整的例子请参考下列文件:          AddRecord2.java、         user.xml。该例子的运行环境为:Windows XP Professional、JDK 1.3.1。为了能够正常编译运行AddRecord2.java这个程序,你需要到网址         http://java.sun.com去下载安装JAXP 1.1或者Java XML Pack(Java XML Pack已经内含JAXP了)。       
         OutputKeys类    
    javax.xml.transform.OutputKeys类和java.util.Properties类配合使用,可以控制JAXP的XSLT引擎(Transformer类)输出XML文档的格式。请看下面的代码片断:
     //首先创建一个TransformerFactory对象,再由此创建Transformer对象。
       tf=TransformerFactory.newInstance(); 
 Transformer t=tf.newTransformer ();
 
 //获取Transformser对象的输出属性,亦即XSLT引擎的缺省输出属性,这是一个java.util.Properties对象。
 Properties properties = t.getOutputProperties();  
 //设置新的输出属性:输出字符编码为GB2312,这样可以支持中文字符,XSLT引擎所输出
 //的XML文档如果包含了中文字符,可以正常显示,不会出现所谓的"汉字问题"。
 //请留意OutputKeys类的字符串常数OutputKeys.ENCODING。
 properties.setProperty(OutputKeys.ENCODING,"GB2312");
 /更新XSLT引擎的输出属性。
 t.setOutputProperties(properties);  
 //调用XSLT引擎,按照输出属性中的设置,输出DOM Tree中的内容到输出介质中。
 t.transform(DOMSource_Object,StreamResult_Object); 
 
   从 上面的程序代码,我们不难看出,通过设置XSLT引擎(Transformer类)的输出属性,可以控制DOM Tree中的内容的输出格式,这对于我们定制输出内容是很有帮助的。那么JAXP的XSLT引擎(Transformer类)有那些输出属性可以设置呢? javax.xml.transform.OutputKeys类定义了很多字符串常数,它们都是可以自由设置的输出属性,常用的输出属性如下所示:
    - public static final java.lang.String METHOD          
可以设为"xml"、"html"、"text"等值。          - public static final java.lang.String VERSION          
所遵循规范的版本号,如果METHOD设为"xml",那么它的值应该设为"1.0",如果METHOD设为"html",那么它的值应该设为"4.0",如果METHOD设为"text",那么这个输出属性会被忽略。          - public static final java.lang.String ENCODING          
设置输出时所采用的编码方式,比如"GB2312"、"UTF-8"等等,如果将其设置为"GB2312",可以解决所谓的"汉字问题"。          - public static final java.lang.String OMIT_XML_DECLARATION          
设置输出到XML文档中时是否忽略XML声明,亦即类似于:          
<?xml version="1.0" standalone="yes" encoding="utf-8" ?>          
这样的代码。它可选的值有"yes"、"no"。          - public static final java.lang.String INDENT          
IDENT设定XSLT引擎在输出XML文档时,是否自动添加额外的空格,它可选的值为"yes"、"no"。          - public static final java.lang.String MEDIA_TYPE          
MEDIA_TYPE设定输出文档的MIME类型。          
    如果设定XSLT引擎的输出属性呢?下面我们来总结一下:
    首先是获取XSLT引擎(Transformer类)的缺省输出属性的集合,这需要使用Transformer类的getOutputProperties()方法,返回值是一个java.util.Properties对象。
    Properties properties = transformer.getOutputProperties();        
然后是设定新的输出属性,比如:        
properties.setProperty(OutputKeys.ENCODING,"GB2312");        
properties.setProperty(OutputKeys.METHOD,"html");        
properties.setProperty(OutputKeys.VERSION,"4.0");        
………………………………………………………       
    最后是更新XSLT引擎(Transformer类)的缺省输出属性的集合,这需要使用Transformer类的setOutputProperties()方法,参数是一个java.util.Properties对象。
    我们编写了一个新的程序,其中应用了OutputKeys类,用以控制XSLT引擎的输出属性,该程序的架构和前一个程序(AddRecord3.java)大致相同,不过输出结果略有不同。完整的代码请参考下列文件:         AddRecord3.java(见附件)、         user.xml(见附件)。该例子的运行环境为:Windows XP Professional、JDK 1.3.1。为了能够正常编译运行         AddRecord3.java这个程序,你需要到网址         http://java.sun.com去下载安装JAXP 1.1或者Java XML Pack(Java XML Pack内含JAXP了)。