Java中Xml解析详解 DOM、SAX、JDOM、DOM4J

1.1 什么是XML

一种表示结构化信息的标准方法,以使计算机能够方便地使用此类信息,并且人们可以非常方便地编写和理解这些信息。XML 是 eXtensible Markup Language(可扩展标记语言)的缩写。www.w3.org/XML/ 上提供了 XML 标准。XML 提供了一种简便的标准方法对数据进行分类,以使其更易于读取、访问以及处理。XML 使用类似于 HTML 的树结构和标签结构.

例如:

[html]  view plain  copy
  1. <?xml version="1.0"?>  
  2. <myfile>  
  3. <title>XML Quick Start</title>  
  4. <author>zhangsan</author>  
  5. <email>zhangsan@hotmail.com</email>  
  6. <date>2013-1-1</date>  
  7. </myfile>  

大部分标签都是我们自己定义的
XML产生的目标:

处理XML文档的程序应该容易编写
在XML中,要求可供选择的特性数量保持绝对的少,更理想一点一个也没有
XML文档应当是可读性强和条理清晰的
XML的设计应当是正规并且简洁的
XML文档应该容易创建
在XML的结构中应该简洁、精练
在Internet上XML应该是直接可以使用的
XML应该支持各种各样的应用
Java与xml是一种完美的组合,Java平台是一种跨平台的编程环境 ,XML是一种跨平台的数据格式 与其他语言相比,Java平台提供了更好的XML支持

1.2 Xml的作用和优点:

主要作用:
存储数据:最基本的办法是采用io流读取
配置文件(用得最多)
XML与Access,Oracle和SQL Server等数据库不同,数据库提供了更强有力的数据存储和分析能力,例如:数据索引、排序、查找、相关一致性等,XML仅仅是展示数据。事实上XML与其他数据表现形式最大的不同是:他极其简单。
主要特点:
支持设计与特定领域相关的标记
自描述数据
不需要借助于其它的工具就可以理解XML中数据的含义,是因为XML中对数据进行了自描述,这保证了只要XML文件本身不被破坏就可以理解所包含数据的含义。同时,XML有很好的规格文档,保证了XML的语义不被误解。
应用间自由交换数据
webservice
结构化和集成的数据


1.3 XML 文档类型:

无效文档:
没有遵守 XML 规范定义的语法规则。
如果开发人员已经在 DTD 或模式中定义了文档能够包含什么,而某个文档没有遵守在其 DTD 或模式中定义的规则
有效文档:既遵守 XML 语法规则也遵守在其 DTD 或模式中定义的规则。 
格式良好(well-formed)的文档:遵守 XML 语法,但没有 DTD 或模式。

1.4 XML 声明 

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
建议使用 XML 声明,但它不是必需的。如有应该在文档开始位置。
声明最多可以包含三个 名称-值 对。
version 是使用的 XML 版本;目前该值必须是 1.0。
encoding 是该文档所使用的字符集。该声明中引用的 ISO-8859-1。如没有指定 encoding,XML 解析器会假定字符在 UTF-8 字符集中,这个字符集是一个几乎支持世界上所有语言的字符和象形文字的 Unicode 标准。
standalone(可以是 yes 或 no),本是独立的意思,定义了是否可以在不读取任何其它文件的情况下处理该文档。例如,如果 XML 文档没有引用任何其它文件,可指定 standalone="yes"。如果 XML 文档引用其它描述该文档可以包含什么的文件可指定 standalone="no",standalone="no" 是缺省的。


1.5 XML 文档中的转义字符

为解决xml文档中各种标记符号的歧义问题,xml提供了如下转义字符:
&lt; 代表小于符号 
&gt; 代表大于符号 
&quot; 代表一个双引号 
&apos; 代表一个单引号(或撇号) 
&amp; 代表一个“与”符号&。

为了避免 XML 解析错误,我们应该使用 &lt; 来替代 <,使用 &amp; 替代 &。但是假设你需要在 XML 文档里写一段内容,里面包含了很多 < 或者 &,要将所有 < 或者 & 转换成实体引用是很讨厌的事情。
这时候,你可以使用 CDATA 区 (CDATA section)。在 CDATA 区里,你可以不必使用实体引用,因为 XML 解析器不会解析 CDATA 区内的内容。
CDATA 区 (CDATA section) 以 <![CDATA[ 开始,以 ]]> 结束。 示例如下:

[html]  view plain  copy
  1. <mycode> This is a html page  
  2. <![CDATA[ 
  3. <html> 
  4.     <head> 
  5.         <title>呵呵</title> 
  6.     </head> 
  7.     <body> 
  8.         I like  
  9.     </body> 
  10. </html> 
  11. ]]></mycode>  

注意:在 CDATA 区内,不能出现 ]]>


1.6 XML语法规则

1、 是否有DTD文件
如果文档是一个"有效的XML文档",那么文档一定要有相应DTD文件,并且严格遵守DTD文件制定的规范。DTD为英文Document Type Definition,中文意思为“文档类型定义”,用于定义这个文档的具体类型,不通场合的xml类型不一样,具体用那一种,要通过DTD定义,所以使用时要指明相应的DTD文件。 

DTD的作用: 

一方面它帮助你编写合法的代码 ,另一方面它让浏览器正确地显示器代码。

DTD文件的声明语句紧跟在XML声明语句后面,格式如下:
<!DOCTYPE type-of-doc SYSTEM/PUBLIC "dtd-name">

其中:

"!DOCTYPE"是指你要定义一个DOCTYPE;
 "type-of-doc"是文档类型的名称,由你自己定义;
 "SYSTEM/PUBLIC"这两个参数只用其一。SYSTEM是指文档使用的私有DTD文件的网址,而PUBLIC则指文档调用一个公用的DTD文件的网址。
 "dtd-name" 就是DTD文件的网址和名称。所有DTD文件的后缀名为".dtd"。


例如用上面的例子,可写成这样:

<?xml version="1.0" standalone="no" encoding="UTF-8"?>

<!DOCTYPE filelist SYSTEM "filelist.dtd">


上面的声明表示:
该xml文件版本是1.0,引用了其他文件,文档字符编码是UTF-8
DTD文档类型名称是filelist,使用的是私有DTD文件的网址,DTD文件为filelist.dtd


2、区分大小写(case-sensitive)

在XML文档中,大小写是有区别的。<P>和<p>是不同的标识。注意在写元素时,前后标识大小写要保持一样。例如:<Author>ajie</Author>,写成<Author>ajie</author>是错误的。


3、属性值

在XML中则规定,所有属性值必须加引号(可以是单引号,也可以是双引号),否则将被视为错误。

 属性名的第一个字母必须以字母或下划线开头

 在同一个开始标记里,一个特殊的属性名只能出现一次


4、所有的标识必须有相应的结束标识

在XML中规定,所有标识必须成对出现,有一个开始标识,就必须有一个结束标识。


5、所有的空标识也必须被关闭

空标识就是标识对之间没有内容的标识。在XML中,规定所有的标识必须有结束标识,针对这样的空标识,XML中处理的方法是在原标识最后加“/”。例如:<br>应写为<br/>;


1.7 XML解析技术(XML常用作作为数据存储格式)

1、DOM(Document object model)
解析器采用了基于树状的模型,侧重结果
优点:

层次清晰,便于存取其中的节点。

缺点:

DOM 构建整个文档驻留内存的树。如果文档很大,就会要求有极大的内存。

 DOM 创建表示原始文档中每个东西的对象,包括元素、文本、属性和空格。如果您只需关注原始文档的一小部分,那么创建那些永远不被使用的对象是极其浪费的。

 DOM 解析器必须在您的代码取得控制权之前读取整个文档。对于非常大的文档,这会引起显著的延迟。

DOM 解析器把 XML 文档转化为一个包含其内容的树,并可以对树进行遍历。然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用 DOM 解析器的时候需要处理整个 XML 文档,所以对性能和内存的要求比较高,尤其是遇到很大的 XML 文件的时候。由于它的遍历能力,DOM 解析器常用于 XML 文档需要频繁的改变的服务中。


2、SAX
解析器采用了基于事件的模型,侧重过程
优点:

快速、适合大文件。

缺点: 

编程较难,仅用于串行存取,由于应用程序不以任何方式存储数据,所以,使用 SAX 时,不可能对数据进行更改。

SAX(Simple API for XML) 不是某个官方标准,但它是 XML 社区事实上的标准,几乎所有的 XML 解析器都支持它。SAX 解析器采用了基于事件的模型,它在解析 XML 文档的时候可以触发一系列的事件,当发现给定的tag(标签)的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。

由于应用程序简单地检查经过其的数据,所以不需要将数据存储在内存里。当遇到大文档时,这是一个突出的优势。SAX 对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX 这种扩展能力得到了更好的体现。但用 SAX 解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。


选择 DOM 还是 SAX,这取决于几个因素:

应用程序的目的:如果必须对数据进行更改,并且作为 XML 将它输出,则在大多数情况下,使用 DOM。

 数据的数量:对于大文件,SAX 是更好的选择。 

 将如何使用数据:如果实际上只使用一小部分数据,则使用 SAX 将数据抽取到应用程序中,这种方法更好些。

 需要速度:通常,SAX 实现比 DOM 实现快。


3、JDOM

注意JDOM的J是Java的意思,决不是DOM扩展,虽然名字差不多,但两者平行的关系。与DOM主要有两方面不同:

首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。

第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快 JDOM 自身不包含解析器。它通常使用 SAX2 解析器来解析和验证输入 XML 文档(尽管它还可以将以前构造的 DOM 表示作为输入)。


4、DOM4J

具有性能优异、功能强大和使用便捷的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。 目前许多开源项目中大量采用DOM4J,例如hibernate也用DOM4J来读取XML配置文件。


1.8 用DOM4J技术解析XML

1、 简介

 dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP。


2、 DOM4J的API文档

从网上可以下载DOM4J的API帮助文档、jar包(如dom4j-1.6.1.jar)、开源代码。其帮助文档首页如下: 

Home是首页,FAQ是常见问题,Quick start是快速入门,主要说明dom4j的基本使用方法,并列举了一写简单的例子。包括如何得到一个xml文档对应的Document对象,(在Java编程环境中模拟的一个文档),通过迭代器(Iterator)遍历xml树,快速循环,创建XML文档,把一个document写到文件中等。Javadoc(1.6.1)就是对应的dom4j对应的类似Java JDK帮助文档一样文档。里面特别常用的接口以斜体方式显示。如Node、Document、Elemet等。


3、 dom4j中常用的接口概述

它的主要接口都在org.dom4j这个包里定义:

Attribute 接口定义了XML的属性 

 Branch接口为能够包含子节点的节点如XML元素(Element)和文档(Docuemnts)定义了一个公共的行为, 

 CDATA接口定义了XML CDATA 区域 ,次区域的大于小于等符号不会被当做标签的符号来读取。

 CharacterData接口是一个标识借口,标识基于字符的节点。如CDATA,Comment, Text. 

 Comment接口定义了XML注释的行为 

 Document 定义了XML文档 

 DocumentType接口定义XML DOCTYPE声明 

 Element Element定义XML 元素 

 ElementHandler接口定义了 Element 对象的处理器 

 ElementPath 被 ElementHandler 使用,用于取得当前正在处理的路径层次信息 

 Entity接口定义xml实体:XML entity 

 Node接口为所有的dom4j中XML节点定义了多态行为 

 NodeFilter接口定义了在dom4j节点中产生的一个滤镜或谓词的行为(predicate) 

 ProcessingInstruction接口定义 XML 处理指令. 

 Text接口定义XML 文本节点. 

 Visitor接口用于实现Visitor模式. 

 XPath接口在分析一个字符串后会提供一个XPath 表达式 


常用接口的继承关系:

java.lang.Cloneable

Node

DocumentType

Attribute

ProcessingInstruction

Entity

Branch

Document

Element

CharacterData

CDATA

Text

Comment

可见,上面关系中,大多都是Node的子节点,这些节点都来自java.org.dom4包中。


4、 Dom4j常用接口、类说明
1) Node接口

asXML():隶属于Node类,用于将XML转换为String

 public Node selectSingleNode(String xpathExpression)可以通过xpath路径返回一个节点。若xpathExpression为"/users/user[id='108']",则得到的是user节点,它有一个子元素id,标签id的值为108.若xpathExpression为"/users/user[@firstname='liu']",则会得到一个user节点,它有个属性是firstname,属性值为liu。

public List selectNodes(String xpathExpression)可以返回所有xpath路径指定的节点


2) SAXReader接口

此接口通过SAX解析事件创建一个DOM4J的SAXReader对象树。常用其无参构造方法SAXReader()。

 public Document read(InputStream in)此方法可以读取一个输入流in,返回一个Java环境下文档Document类型的对象。SAXReader的read方法是重载的,可以从InputStream, File, Url等多种不同的源来读取。得到的Document对象就带表了整个XML。读取的字符编码是按照XML文件头定义的编码来转换。如果遇到乱码问题,注意要把各处的编码名称保持一致即可。

3) Document接口

此接口是Node的子接口,用于定义一个XML文档,没有自己的构造方法,有两种方法得到Document对象:一是从SAXReader的read方法读流得到,二是由DocumentHelper类的静态方法createDocument得到。

 public Element getRootElement() 此方法可以得到这个Document的根元素,也就是树的根。

 public Element addElement(String name)此方法是从Document的父类Node那里继承而来的, 可以给Document添加一个名为name元素,并返回这个元素。

 public void add(Element element)此方法也是从Node那里继承而来,可以添加一个元素到该document的分支上。如果传进来的元素已经有了父节点,就会抛出非法异常。


4) Element接口

此接口是Node、Branch的子接口,用于定义一个XML元素,也就是树的节点,一个元素可以设置命名空间,属性,子节点和文本内容。

 public Element element(String name) 此方法可以得到该元素下面名字为name的子元素

 public List elements() 此方法可以得到一个存储该元素下面所有元素的List表。

 public String getText() 此方法可以返回该元素的文本内容,如果内容为空则返回一个空字符串而不是null。

 public String getTextTrim() 返回该元素所含有的text内容,其中连续的空格被转化为单个空格,该方法不会返回null

 public String attributeValue(String name) 此方法可以得到该元素名为name的属性的值,如果xml文档中没有指明该属性,就返回其默认值。

 public String attributeValue(String name,String defaultValue)该方法返回该元素名为name属性的值,如果xml文档中没有指明,就返回传给此方法的dafaultValue,这可能并不是该属性的默认值。

 public Element addAttribute(String name,String value)此方法可以给该元素名为name的属性设置值,并覆盖该属性先前的值。返回该元素。 

 部分其他方法简介

MethodComment

getQName()元素的QName对象

getNamespace()元素所属的Namespace对象

getNamespacePrefix()元素所属的Namespace对象的prefix

getNamespaceURI()元素所属的Namespace对象的URI

getName()获取该元素的local name

getQualifiedName()元素的qualified name

attributeIterator()元素属性的iterator,其中每个元素都是Attribute对象

attributeValue()元素的某个指定属性所含的值

elementIterator()元素的子元素的迭代器Iterator,其中每个元素都是Element对象

element()元素的某个指定(qualified name或者local name)的子元素

elementText()元素的某个指定(qualified name或者local name)的子元素中的text信息

getParent元素的父元素

getPath()元素的XPath表达式,其中父元素的qualified name和子元素的qualified name之间使用"/"分隔

isTextOnly()是否该元素只含有text或是空元素

detach() 移除自己

isRootElement()是否该元素是XML树的根节点

selectNodes(String xpathExpression)通过XPATH获取节点。

selectSingleNode(String xpathExpression)通过XPATH获取一个节点。

getDocument()作为一个Document返回


5) OutputFormat类

问题:把做好的xml文档直接用流输出,输出的方式默认为紧凑的,才用UTF-8编码,遇到中文字符就会显示为乱码。如何用很清晰的缩进的格式显示呢。

 说明:此类位于java.org.dom4j.io包中,用于格式化XML文档的输出。

此类的使用方式是将此类的对象作为参数传递给XMLWriter类的构造方法,从而构建一个XML输出流类,调用其write方法进行输出。

 public static OutputFormat createPrettyPrint()此静态方法不是OutputFormat的构造方法,但该方法可以创建一个设定好XML输出格式的OutputFormat对象。此默认格式就是,元素标签之间换行,并且子元素与父元素间产生2个空格的缩进,且换行、缩进中空的部分都是空白。

 public void setIndent(String indent) 该方法可以设置该类对象的XML文档输出时,子元素与父元素间缩进填充的字符串indent。字符串indent将会显示在XML文档子元素与父元素间缩进的位置。

 public void setEncoding(String encoding)此方法可以设定输出的XML文档的编码方式,如果是GBK就可正确显示文档的中文信息。


6) XMLWriter类

此类是dom4j.io中的类,可以得到一个dom4j树,把他按xml文件格式的流。其有参构造方法可以接受一个设定好xml文档格式的OutputFormat,并按照这个OutputFormat给定格式输出xml文档。写入文件后,要调用此类的flush方法刷新缓冲区,在关闭流。

 public XMLWriter(OutputStream out, OutputFormat format)

给构造方法,就收一个输出流对象和一个OutputForemat对象。out可以指定XMLWriter输出XML文档的地方,format指定输出格式。

 write方法

此方法进行了多次重载,可以写Document、Element、String等等。


7) DocumentHelper

createDocument():创建一个Document对象

 parseText(String text):解析给定Xml的文本,生成Document对象 


5、 用DOM4解析技术操作xml文档

1) 几种存储方式:

普通txt文件------存取不便

 数据库 -----------没必要

 技术 -------------结构清晰,层次明了


Xml与数据库中表的关系:

根元素-----表名

 子元素-----记录

 子元素中的子元素---------字段 


2) 生成xml文档:

得到文件输出流:新建File对象,创建FileWriter输出流

 设置生成文档格式:创建OutputFromat对象设置

 创建输出流:创建XMLWriter,构造方法得到输出流、设定的格式

 写文件到磁盘:把已有的Document对象通过XMLWriter的write方法写入磁盘

 刷新、关闭流


3) 获取xml文档:

找到文件:通过磁盘xml文件,new一个File对象

 打开文件:通过得到的文件对象创建一个InputStream

 把InputStream变成树:创建一个SAXReader对象,通过其read方法读文件对象或输入流对象得到一个Document对象


4) 查询xml文档的叶子节点

找树根:Document的getRootElement方法可以得到SAXReader创建的树的类型为Element的根节点。

 找树枝:Element接口中的elements()可以找到该元素下的所有节点

 找树叶:用element方法可以找到指定名称的子节点

 关闭流


Xml path简称xpath
问题:查找过程浪费时间在一些中间节点,如果中间节点过多则性能下降。
定义:由xml中的节点或者属性构成的序列。

XPath是用来搜索和查询XML文档,以得到与给定标准相匹配的节点列表的语言。它已经被W3C标准化了。一个XPath语言的表达式可详细说明要匹配的位置和模式。将布尔运算符、字符串运算符和算术运算符应用到XPath语言的表达式中,能够建立极其复杂的查询.

作用:根据条件(某个节点或者属性)快速定位某个节点。
关系:
Users----user-----id

/users/user[id='108']

借助子节点的值来定位父节点

条件是id的值

定位的节点是user

/users/user[@firstname='liu']

借助节点属性值来定位所在的节点。


XPath语言的特性

能找到当前节点的所有子节点

 能找到具有特定标志的当前上下文节点的所有祖先节点

 能找到具有特定标志的当前节点的最后一个子元素

 根据一个给定属性的元素的当前上下文节点找到第n个元素

 能找到具有<tag1>或<tag2>标志的第一个子元素

 能找到不具有某个属性的元素的所有子节点

 能找到所有数字元素子节点的总和

 能找到所有子节点的数量


待解析的xml:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <students count="1">   
  3.    <student aa="abc">   
  4.         <sid>119</sid>    
  5.         <sname>刘晓东</sname>    
  6.         <major>computer</major>  
  7.         <password>34567</password>    
  8.         <birth>1997-08-01</birth>    
  9.         <address>USA</address>    
  10.         <gender>m</gender>   
  11.     </student>  
  12.     <student firstname="liu">   
  13.         <sid>121</sid>    
  14.         <sname>晓东</sname>    
  15.         <major>computer</major>  
  16.         <password>34567</password>    
  17.         <birth>1997-08-01</birth>    
  18.         <address>USA</address>    
  19.         <gender>m</gender>   
  20.     </student>  
  21.      <student>   
  22.         <sid>120</sid>    
  23.         <sname>刘晓东</sname>  
  24.         <major>computer</major>    
  25.         <password>34567</password>    
  26.         <birth>1997-08-01</birth>    
  27.         <address>USA</address>    
  28.         <gender>m</gender>   
  29.     </student>   
  30. </students>  



示例部分代码如下:

[java]  view plain  copy
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.InputStream;  
  6.   
  7. import org.dom4j.Document;  
  8. import org.dom4j.DocumentException;  
  9. import org.dom4j.io.OutputFormat;  
  10. import org.dom4j.io.SAXReader;  
  11. import org.dom4j.io.XMLWriter;  
  12.   
  13. /**对Document的一些操作都放在此类管理 
  14.  * 1.通过xml文件得到Document对象 
  15.  * 2.对已有的Document对象,把他按照我们的格式写到磁盘中 
  16.  */  
  17. public class DocumentManager {  
  18.   
  19.     private static String fileName = "src/Users.xml";  
  20.     public static Document getDocument(){  
  21.         return getDocument(fileName);  
  22.     }  
  23.     public static Document getDocument(String fileName){  
  24.         try {         
  25.             File file = new File(fileName);  
  26.             InputStream is = new FileInputStream(file);  
  27.             SAXReader reader = new SAXReader();  
  28.             Document document = reader.read(is);  
  29.             return document;  
  30.         } catch (Exception e) {  
  31.             e.printStackTrace();  
  32.         }   
  33.         return null;  
  34.     }  
  35.       
  36.     public static void writeDocument(Document document){  
  37.         writeDocument(document,fileName);  
  38.     }  
  39.     public static void writeDocument(Document document,String filename){  
  40.         //1.set the format  
  41.         OutputFormat format = OutputFormat.createPrettyPrint();  
  42.         //2.set the indent to 4 white spaces.  
  43.         format.setIndent("    ");         
  44.         try {             
  45.             XMLWriter writer = new XMLWriter(new FileOutputStream(filename),format);              
  46.             writer.write(document);   
  47.             writer.flush();  
  48.             writer.close();           
  49.         } catch (Exception e) {  
  50.             // TODO Auto-generated catch block  
  51.             e.printStackTrace();  
  52.         }     
  53.     }  
  54.       
  55.     public static void main(String[] args) {  
  56.          getDocument();  
  57.   
  58.     }  
  59.   
  60. }  


[java]  view plain  copy
  1. import java.sql.Date;  
  2.   
  3. /**创建一个程序中实体对应xml中的信息,更加人性化,用户也更容易 
  4.  * 理解 当然这就需要xml中的东西与实体间进行转换,包含对象、操作等  
  5.  * 1.创建一个用户类User,对应xml中的Student,用户的属性对应xml中Student所有子元素 
  6.  * 2.实体的各属性都要有set、get方法,以便设置、获取属性值 
  7.  * 3.尤其是对xml的文档的元素进行排序,如果用xml里面的元素排,在排的时候要把元素从他们的父节点上移除 
  8.  *   添加的时候还要设置被被添加的子元素的父节点为null,很是麻烦,用把Element转换成为User对象,排好了序 
  9.  *   再写到原来的xml文档,这样就简单很多。 
  10.  * 4.重写toString方法便于查看属性 
  11.  */  
  12. public class User {  
  13.     private Integer id = null;  
  14.     private String username =  null;  
  15.     private String password = null;  
  16.     private Date   birth = null;  
  17.     private String address = null;  
  18.     private String gender = null;  
  19.       
  20.     public Integer getId() {  
  21.         return id;  
  22.     }  
  23.     public void setId(Integer id) {  
  24.         this.id = id;  
  25.     }  
  26.     public String getUsername() {  
  27.         return username;  
  28.     }  
  29.     public void setUsername(String username) {  
  30.         this.username = username;  
  31.     }  
  32.     public String getPassword() {  
  33.         return password;  
  34.     }  
  35.     public void setPassword(String password) {  
  36.         this.password = password;  
  37.     }  
  38.     public Date getBirth() {  
  39.         return birth;  
  40.     }  
  41.     public void setBirth(Date birth) {  
  42.         this.birth = birth;  
  43.     }  
  44.     public String getAddress() {  
  45.         return address;  
  46.     }  
  47.     public void setAddress(String address) {  
  48.         this.address = address;  
  49.     }  
  50.     public String getGender() {  
  51.         return gender;  
  52.     }  
  53.     public void setGender(String gender) {  
  54.         this.gender = gender;  
  55.     }  
  56.       
  57.     @Override  
  58.     public String toString(){  
  59.         return "id=" + id + "\tusername=" + username + "\tpassword=" + password  
  60.                 + "\tgender="+gender+"\taddress"+address+"\tbirth="+birth;  
  61.     }  
  62. }  
  63.   
  64.   
  65. import java.util.List;  
  66.   
  67. /**创建一个接口,让人一步了然的看清有什么方法,做了写什么事, 
  68.  * 还可以实现接口 
  69.  */  
  70. public interface IUserDAO {  
  71.     public List<User> getAllUser();  
  72.     public User getUserByUsername(String username);  
  73.     public List<User> getUserListByUsername(String username);  
  74.     public List<User> getUserListByKeyword1(String keyword);  
  75.     public List<User> getUserListByKeyword2(String keyword);  
  76.     public boolean addUser(User user);  
  77.     public boolean addUserWithUniquePK(User user)throws UserExistsException;  
  78.     public boolean addUserWithAutoPK(User user);  
  79.     public boolean deleteUserByPK(int id);  
  80.     public boolean update(User user);  
  81.     public void sortUserList(List<User> userList);  
  82.     public void sortUserList(List<User> userList,boolean ascendable);  
  83.     public void testXPath1();  
  84.     public User getUserById(int id);  
  85.     public void sortById1();  
  86.     public void sortById2();  
  87.     public void sortById3();  
  88.     public void sortById4();  
  89. }  


[java]  view plain  copy
  1. import java.sql.Date;  
  2. import java.util.ArrayList;  
  3. import java.util.Collections;  
  4. import java.util.Comparator;  
  5. import java.util.List;  
  6.   
  7. import org.dom4j.Document;  
  8. import org.dom4j.DocumentHelper;  
  9. import org.dom4j.Element;  
  10. import org.dom4j.Node;  
  11. import org.dom4j.io.SAXReader;  
  12.   
  13. public class UserDAO implements IUserDAO {  
  14.     /** 
  15.      * 把一个Element对象转换成一个User对象  
  16.      * 1.得到Element各子元素存储的信息 
  17.      * 2.把这些String类型的信息转换成相应的类型后赋值给User对象的属性 
  18.      * 3.返回User对象 
  19.      */  
  20.     private User getObjectFromElement(Element userElement) {  
  21.         String idStr = userElement.elementTextTrim("id");  
  22.         String username = userElement.elementTextTrim("username");  
  23.         String birthStr = userElement.elementTextTrim("birth");  
  24.         String gender = userElement.elementTextTrim("gender");  
  25.         String password = userElement.elementTextTrim("password");  
  26.         String address = userElement.elementTextTrim("address");  
  27.   
  28.         Integer id = Integer.parseInt(idStr);  
  29.         // convert a string in the format "yyyy-mm-dd" into a Date type.  
  30.         Date birth = Date.valueOf(birthStr);  
  31.         User user = new User();  
  32.         user.setId(id);  
  33.         user.setUsername(username);  
  34.         user.setPassword(password);  
  35.         user.setBirth(birth);  
  36.         user.setAddress(address);  
  37.         user.setGender(gender);  
  38.   
  39.         return user;  
  40.     }  
  41.   
  42.     // 通过指定的用户名得到xml中第一个与指定用户名相同的用户对应的User对象  
  43.     public User getUserByUsername(String username) {  
  44.         try {  
  45.             Document document = DocumentManager.getDocument();  
  46.             Element usersElement = document.getRootElement();  
  47.             List<Element> userElementList = usersElement.elements();  
  48.             for (Element userElement : userElementList) {  
  49.                 Element usernameElement = userElement.element("username");  
  50.                 String _username = usernameElement.getText();  
  51.                 if (username.equals(_username) == false) {  
  52.                     continue;  
  53.                 }  
  54.                 return this.getObjectFromElement(userElement);  
  55.             }  
  56.   
  57.         } catch (Exception e) {  
  58.             e.printStackTrace();  
  59.         }  
  60.         return null;  
  61.     }  
  62.   
  63.     public List<User> getAllUser() {  
  64.         return null;  
  65.     }  
  66.   
  67.     /** 
  68.      * 通过指定的字符串查找到xml文件中用户名中包含此字符串的所有User对象 
  69.      * 1.得到xml对应的Docuement对象,寻找到xml中所有的Student元素  
  70.      * 2.创建一个存储User对象的List列表 
  71.      * 3.让Student的子元素也就是叶子节点存储的信息与我们想找的username比较 
  72.      * 4.把匹配的转换成为User对象后添加到List中 
  73.      * 5.返回这个List 
  74.      */  
  75.     @Override  
  76.     public List<User> getUserListByUsername(String username) {  
  77.         Document document = DocumentManager.getDocument();  
  78.         Element usersElement = document.getRootElement();  
  79.         List<Element> userElementList = usersElement.elements();  
  80.         List<User> userList = new ArrayList<User>();  
  81.         // 创建一个ArrayList,这样的数组列表有序、可以重复、长度可变,  
  82.         // 用于存储我们找到的User对象  
  83.         for (Element userElement : userElementList) {  
  84.             String _username = userElement.elementTextTrim("username");  
  85.             if (_username.contains(username)) {  
  86.                 User user = this.getObjectFromElement(userElement);  
  87.                 userList.add(user);  
  88.             }  
  89.         }  
  90.         return userList;  
  91.     }  
  92.   
  93.     /** 
  94.      * 查询所有包含我们指定字符串内容信息的用户,并转换成User对象返回  
  95.      * 1.创建一个存储User对象的ArrayList 
  96.      * 2.找到存有所有用户元素列表,循环找到各个user节点  
  97.      * 2.找到其字节点叶子节点,看他们的内容是否包含我们要查询的信息keywword 
  98.      * 3.如果遇到任何一个叶子结点包含,就不再找这个user的后面元素了 
  99.      * 4.满足条件的user节点转换成user对象,添加到List中 
  100.      * 5.返回List 
  101.      */  
  102.     public List<User> getUserListByKeyword1(String keyword) {  
  103.         Document document = DocumentManager.getDocument();  
  104.         Element usersElement = document.getRootElement();  
  105.         List<Element> userElementList = usersElement.elements();  
  106.   
  107.         List<User> userList = new ArrayList<User>();  
  108.         for (Element userElement : userElementList) {  
  109.             String _username = userElement.elementTextTrim("username");  
  110.             if (_username.contains(keyword)) {  
  111.                 User user = this.getObjectFromElement(userElement);  
  112.                 userList.add(user);  
  113.                 continue;  
  114.                 // 如果这叶子节点包含我们想找的信息,就添加到List中,并终止本次循环  
  115.                 // 不用在查这个user元素后面的子元素是否包含keyword  
  116.             }  
  117.             String password = userElement.elementTextTrim("password");  
  118.             if (password.contains(keyword)) {  
  119.                 User user = this.getObjectFromElement(userElement);  
  120.                 userList.add(user);  
  121.                 continue;  
  122.             }  
  123.             String birth = userElement.elementTextTrim("birth");  
  124.             if (birth.contains(keyword)) {  
  125.                 User user = this.getObjectFromElement(userElement);  
  126.                 userList.add(user);  
  127.                 continue;  
  128.             }  
  129.             String address = userElement.elementTextTrim("address");  
  130.             if (address.contains(keyword)) {  
  131.                 User user = this.getObjectFromElement(userElement);  
  132.                 userList.add(user);  
  133.                 continue;  
  134.             }  
  135.             String gender = userElement.elementTextTrim("gender");  
  136.             if (gender.contains(keyword)) {  
  137.                 User user = this.getObjectFromElement(userElement);  
  138.                 userList.add(user);  
  139.                 continue;  
  140.             }  
  141.         }  
  142.         return userList;  
  143.     }  
  144.   
  145.     /** 
  146.      * 与上面的getUserListByKeyword2功能一样 
  147.      */  
  148.     public List<User> getUserListByKeyword2(String keyword) {  
  149.         Document document = DocumentManager.getDocument();  
  150.         Element usersElement = document.getRootElement();  
  151.         List<Element> userElementList = usersElement.elements();  
  152.         List<User> userList = new ArrayList<User>();  
  153.         for (Element userElement : userElementList) {  
  154.             List<Element> userSubelementList = userElement.elements();  
  155.   
  156.             // traverse every sub-element of the user element.  
  157.             for (Element userSubelement : userSubelementList) {  
  158.                 String elementValue = userSubelement.getTextTrim();  
  159.                 if (elementValue.contains(keyword)) {  
  160.                     // if the sub-element is located successfully,other  
  161.                     // sub-element is not read any longer.  
  162.                     User user = this.getObjectFromElement(userElement);  
  163.                     userList.add(user);  
  164.                     break;  
  165.                     // 如果找到了user的某个子元素包含keyword,就跳出访问其  
  166.                     // 子元素的这个循环,接着外层查找下个user的子节点是否包含  
  167.                 }  
  168.             }  
  169.         }  
  170.         return userList;  
  171.     }  
  172.   
  173.     /** 
  174.      * 把一个User对象user,把它转换成为Element类型的user节点 
  175.      * 1.创建名为user的Element对象 
  176.      * 2.通过Element的addElement方法给user添加子元素  
  177.      * 3.设置子元素中的内容为User对象对应属性的内容 
  178.      * 4.返回一个Element元素 
  179.      */  
  180.     private Element getElementFromObject(User user) {  
  181.         Element userElement = DocumentHelper.createElement("user");  
  182.         // dom4j中的大多对象都不是构造方法生成的,而是用DcumentHelper来creat的  
  183.         // 包括文档、元素、属性、文本等都可以依次来生成  
  184.         userElement.addElement("id").setText(user.getId() + "");  
  185.         userElement.addElement("username").setText(user.getUsername());  
  186.         userElement.addElement("password").setText(user.getPassword());  
  187.         userElement.addElement("birth").setText(user.getBirth() + "");  
  188.         userElement.addElement("address").setText(user.getAddress());  
  189.         userElement.addElement("gender").setText(user.getGender());  
  190.         return userElement;  
  191.     }  
  192.   
  193.     /** 
  194.      * 添加User对象的用户user到XML文档中  
  195.      * 1.把User对象user转换成为Element类型的元素 
  196.      * 2.得到指定xml文档的Document对象,找到要添加元素的节点  
  197.      * 3.通过addElement方法把要添加的元素填进去 
  198.      * 4.更改计算user节点元素个数的count属性,每添加一个就加1  
  199.      * 5.把我们修改后的Document对象写到磁盘 
  200.      */  
  201.     @Override  
  202.     public boolean addUser(User user) {  
  203.         Element userElement = this.getElementFromObject(user);  
  204.         Document document = DocumentManager.getDocument();  
  205.         Element usersElement = document.getRootElement();  
  206.         usersElement.add(userElement);  
  207.         this.setCountAttribute(usersElement, true);  
  208.         DocumentManager.writeDocument(document);  
  209.         return false;  
  210.     }  
  211.   
  212.     /** 
  213.      * 设置usersElement的计user数的属性count  
  214.      * 1.通过attributeValue方法得到该元素的指定属性的值  
  215.      * 2.转换成为整数类型 
  216.      * 3.increment为false表示是增加元素,count就增加,反之减少 
  217.      * 4.把count转换成字符串后通过addAtributte方法把设定好的属性添加到原节点中 
  218.      */  
  219.     private void setCountAttribute(Element usersElement, boolean increment) {String countStr = usersElement.attributeValue("count");int count = 0;try {count = Integer.parseInt(countStr);} catch (NumberFormatException e) {  
  220.     // TODO Auto-generated   
  221.     catch blocke.printStackTrace();}if (increment) {count++;} else {count--;}  
  222.     //addAttribute方法可以向元素中添加属性  
  223.     usersElement.addAttribute("count", count + "");  
  224.     }  
  225.   
  226.     /** 
  227.      * 添加id唯一的用户,一旦id相同就抛出异常。  
  228.      * 1.得到元素id  
  229.      * 2.坚持要插入元素的id是否已经存在  
  230.      * 3.如果不存在就添加元素,如果存在就抛出异常 
  231.      * 这写操作是在转换成Element前做的,只是对User对象,这样更简便 * 
  232.      */  
  233.     @Overridepublic  
  234.     boolean addUserWithUniquePK(User user) throws UserExistsException {  
  235.         Document document = DocumentManager.getDocument();  
  236.         if (this.findUserElementById(document, user.getId()) != null) {  
  237.             throw new UserExistsException("The user DOES EXIST!");  
  238.         }  
  239.         this.addUser(user);  
  240.         return false;  
  241.     }  
  242.   
  243.     @Overridepublic  
  244.     boolean addUserWithAutoPK(User user) {  
  245.         // PK是primary key的缩写,主键的意思,这里就是id号  
  246.         // 自动设置用户id,找出现有id号最大的,让新加入的用户id号比最大的大一就可以了  
  247.         Document document = DocumentManager.getDocument();  
  248.         Element usersElement = document.getRootElement();  
  249.         List<Element> userElementList = usersElement.elements();  
  250.         int maxId = 0;  
  251.         for (Element userElement : userElementList) {  
  252.             String idStr = userElement.elementTextTrim("id");  
  253.             int id = 0;  
  254.             try {  
  255.                 id = Integer.parseInt(idStr);  
  256.             } catch (NumberFormatException e) {  
  257.                 e.printStackTrace();  
  258.             }  
  259.             if (maxId < id) {  
  260.                 maxId = id;  
  261.             }  
  262.         }  
  263.         maxId++;  
  264.         user.setId(maxId);  
  265.         this.addUser(user);  
  266.         return false;  
  267.     }  
  268.   
  269.     /** 通过id找到一个Document对象中的元素 */  
  270.     private Element findUserElementById(Document document, int id) {  
  271.         Element usersElement = document.getRootElement();  
  272.         List<Element> userElementList = usersElement.elements();  
  273.         for (Element userElement : userElementList) {  
  274.             // Step 2:  
  275.             String idStr = userElement.elementTextTrim("id");  
  276.             if (idStr.equals(id + "")) {  
  277.                 return userElement;  
  278.             }  
  279.         }  
  280.         return null;  
  281.     }  
  282.   
  283.     /** 
  284.      * 删除指定id的节点   
  285.      * 1.通过id找到要删除的节点 
  286.      *  2.找到要删除节点的父节点,可以用父接口中的getParent找到,在这里就是根节点 
  287.      * 3.判断要删除的节点是否为空  
  288.      * 4.通过父节点,调用remove方法删除要删除的节点  
  289.      * 5.如果删除成功,就让计算user节点个数的减少 
  290.      * 6.把修改过的Document对象写到原xml文件中 * 
  291.      */  
  292.     public boolean deleteUserByPK(int id) {  
  293.         Document document = DocumentManager.getDocument();  
  294.         Element userElement = this.findUserElementById(document, id);  
  295.         if (userElement != null) {  
  296.             Element usersElement = document.getRootElement();  
  297.             if (usersElement.remove(userElement)) {  
  298.                 // 执行remove方法的同时也做了判断条件,移除后才能设置计数的属性  
  299.                 // alter the count attribute if succeeded in removing the  
  300.                 element.this.setCountAttribute(usersElement, false);  
  301.             }  
  302.             // Step 4:re-write the document into the  
  303.             disc.DocumentManager.writeDocument(document);  
  304.         }  
  305.         return false;  
  306.     }  
  307.   
  308.     /** 
  309.      * 用户传入一个User对象,更新xml文档中对应元素的信息  
  310.      * 1.得到要更新的xml文档的document 
  311.      * 2.由于id是唯一的,通过User对象中的id属性值,找到xml文档中对应id的元素 
  312.      * 3.得到这个元素的父节点,可以用getParent方法,这里是根节点  
  313.      * 4.通过父节点调用remove方法移除原来的节点 
  314.      * 5.把User对象转换成Element对象,添加到document中  
  315.      * 6.写更新后的document到xml文档 
  316.      */  
  317.     @Overridepublic  
  318.     boolean update(User user) {  
  319.         Document document = DocumentManager.getDocument();  
  320.         Element userElement = this.findUserElementById(document, user.getId());  
  321.         Element usersElement = document.getRootElement();  
  322.         if (usersElement.remove(userElement)) {  
  323.             userElement = this.getElementFromObject(user);  
  324.             usersElement.add(userElement);  
  325.         }  
  326.         DocumentManager.writeDocument(document);  
  327.         return false;  
  328.     }  
  329.   
  330.     /** 
  331.      * 对一个存储User对象的UserList中的元素排序  
  332.      * 1.构建比较器对象,指定排序标准  
  333.      * 2.用Conections的sort方法排序 
  334.      */  
  335.     @Overridepublic  
  336.     void sortUserList(List<User> userList, boolean ascendable) {  
  337.         // Step 1:make a sort  
  338.         standard.Comparator<User> userComparator = new UserComparator(  
  339.                 ascendable);  
  340.         // Step 2:relate the sort standard to the sorted objects  
  341.         // 调用Collections的静态方法sort根据排序标准排序  
  342.         Collections.sort(userList, userComparator);  
  343.     }  
  344.   
  345.     @Overridepublic  
  346.     void sortUserList(List<User> userList) {  
  347.         this.sortUserList(userList, true);  
  348.         // 默认排序顺序是升序  
  349.     }  
  350.   
  351.     /** 
  352.      * 通过xpath来快速查找  
  353.      * 1.设置xpath路径,是字符串的形式,XPath技术可以通过子元素名、自己属性名来获得节点,  
  354.      *    并可以进而找到其父节点、子节点、元素等 
  355.      * 2.可以把xpath字符串路径传递给selectSingleNode、selectNodes方法,这两个方法来自Node, 
  356.      * 来得到Node实例调用这两个方法的对象是说指XPath路径的父节点 
  357.      */  
  358.     @Overridepublic  
  359.     void testXPath1() {  
  360.         // xpath可以根据条件(某个节点或者属性)快速定位某个节点。  
  361.         // locate user node whose sub-element id value is 121  
  362.         String xpath = "/users/user[id='121']";  
  363.           
  364.         // 通过id值查找节点  
  365.         userDocument document = DocumentManager.getDocument();  
  366.         Node userElement = document.selectSingleNode(xpath);  
  367.           
  368.         // selectSingleNode方法来自Node,可以通过xpath字符串路径得到一个Node实例  
  369.         // 此方法在此处返回的实例是Element对象,拥有子节点id为121的user节点  
  370.         System.out.println(this.getObjectFromElement((Element) userElement));  
  371.         System.out.println("username="  
  372.                 + ((Element) userElement).elementText("username"));  
  373.         String xpath = "/users/user[id='121']/username";  
  374.         Document document = DocumentManager.getDocument();  
  375.         Node userElement = document.selectSingleNode(xpath);  
  376.           
  377.         // 此时selectSingleNode得到的是一个user节点的子元素username,  
  378.         // 该user节点有一个元素id,他的值为121  
  379.         System.out.println("username=" + ((Element) userElement).getTextTrim());  
  380.         String xpath = "/users/user[@firstname='liu']/birth";  
  381.           
  382.         // 通过属性访问,这里的selectSingleNode方法得到的是user的一个子元素birth,  
  383.         // 这个user有个属性叫firstname,属性值为liu  
  384.         Element birthElement = (Element) DocumentManager.getDocument()  
  385.                 .getRootElement().selectSingleNode(xpath);    
  386.     }  
  387.   
  388.     /** 
  389.      * 通过id得到用户User对象 
  390.      *  1.通过指定指定id建立可变的XPath字符串路径  
  391.      *  2.通过XPath路径得到xml文档中对应的元素 
  392.      *  3.把找到的元素转换成为User对象类型返回 
  393.      */  
  394.     @Overridepublic  
  395.     User getUserById(int id) {  
  396.         String xpath = "/users/user[id='" + id + "']";  
  397.         System.out.println("xpath=" + xpath);  
  398.         Element userElement = (Element) DocumentManager.getDocument()  
  399.                 .selectSingleNode(xpath);  
  400.         return this.getObjectFromElement(userElement);  
  401.     }  
  402.   
  403.     /** 
  404.      * 让xml文档按照id进行排序 * 保留document,建新的根节点users  
  405.      * 1.得到原xml文档的document  
  406.      * 2.创建一个List 
  407.      * 3.取原来xml文档的所有等待排序的user元素存在List中 
  408.      * 4.把所有要排序的user元素从原根节点中移除来,这样他们才“自由”了,才可以进行排序,否则会抛出异常  
  409.      * 5.对List中所有元素排序 * 
  410.      * 6.设置List中所有元素的父节点为null  
  411.      * 7.新建一个Element对象users 
  412.      * 8.把排好序的List中的元素依次添加到users下面,添加一个节点为子元素时,必须确认其父元素为空 
  413.      * 9.在document下面移除原来的根节点,设置users为根节点  
  414.      * 10.写document到xml文档 
  415.      */  
  416.     public void sortById3() {  
  417.         List<Element> userElementList = rootElement.elements("user");  
  418.         // 把原来的所有用户存入List中  
  419.         Element newRootElement = DocumentHelper.createElement("users");  
  420.           
  421.         // System.out.println("newRootElement父节点为"+newRootElement.getParent());  
  422.         // 重新创建一个users的节点  
  423.         for (Element userElement : userElementList) {  
  424.             rootElement.remove(userElement);  
  425.         }  
  426.           
  427.         // 把原来根节点的所有内容从原来的根节点移除出去,这样这些节点才是自由的,才可以排序?  
  428.         List l = rootElement.elements();  
  429.         System.out  
  430.                 .println("rootElement.elements() 有 " + rootElement.elements());  
  431.         Comparator userElementComparator = new UserElementComparator();  
  432.         Collections.sort(userElementList, userElementComparator);  
  433.           
  434.         // sort方法根据Comparator中的compare方法的结果进行排序,Comparator是个比较器。  
  435.         // 此实现将指定列表转储到一个数组中,并对数组进行排序,在重置数组中相应位置每个元素的列表上进行迭代。  
  436.         for (Element userElement : userElementList) {  
  437.             System.out.println(userElement.elementTextTrim("id"));  
  438.         }  
  439.         for (Element userElement : userElementList) {  
  440.             // System.out.println("userElement父节点是:"+userElement.getParent());  
  441.             userElement.setParent(null);  
  442.             // System.out.println("userElement父节点变成了:"+userElement.getParent());  
  443.             // System.out.println();  
  444.             // 设置userElement的父节点为空newRootElement.add(userElement);  
  445.             // 在新的根节点上添加元素}newRootElement.setAttributeValue("count",  
  446.             // userElementList.size() + "");  
  447.             // 为新的根节点添加属性document.setRootElement(newRootElement);  
  448.             // setRootElement可以设置document的根节点DocumentManager.writeDocument(document);}}  
  449.         }  
  450.     }  
  451. }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值