天天看点

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

1.1 什么是XML

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

例如:

<?xml version="1.0"?>
<myfile>
<title>XML Quick Start</title>
<author>zhangsan</author>
<email>[email protected]</email>
<date>2013-1-1</date>
</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[ 开始,以 ]]> 结束。 示例如下:

<mycode> This is a html page
<![CDATA[
<html>
	<head>
		<title>呵呵</title>
	</head>
	<body>
		I like 
	</body>
</html>
]]></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:

<?xml version="1.0" encoding="UTF-8"?>
<students count="1"> 
   <student aa="abc"> 
        <sid>119</sid>  
        <sname>刘晓东</sname>  
        <major>computer</major>
        <password>34567</password>  
        <birth>1997-08-01</birth>  
        <address>USA</address>  
        <gender>m</gender> 
    </student>
    <student firstname="liu"> 
        <sid>121</sid>  
        <sname>晓东</sname>  
        <major>computer</major>
        <password>34567</password>  
        <birth>1997-08-01</birth>  
        <address>USA</address>  
        <gender>m</gender> 
    </student>
     <student> 
        <sid>120</sid>  
        <sname>刘晓东</sname>
        <major>computer</major>  
        <password>34567</password>  
        <birth>1997-08-01</birth>  
        <address>USA</address>  
        <gender>m</gender> 
    </student> 
</students>           

示例部分代码如下:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

/**对Document的一些操作都放在此类管理
 * 1.通过xml文件得到Document对象
 * 2.对已有的Document对象,把他按照我们的格式写到磁盘中
 */
public class DocumentManager {

	private static String fileName = "src/Users.xml";
	public static Document getDocument(){
		return getDocument(fileName);
	}
	public static Document getDocument(String fileName){
		try {		
			File file = new File(fileName);
			InputStream is = new FileInputStream(file);
			SAXReader reader = new SAXReader();
			Document document = reader.read(is);
			return document;
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return null;
	}
	
	public static void writeDocument(Document document){
		writeDocument(document,fileName);
	}
	public static void writeDocument(Document document,String filename){
		//1.set the format
		OutputFormat format = OutputFormat.createPrettyPrint();
		//2.set the indent to 4 white spaces.
		format.setIndent("    ");		
		try {			
			XMLWriter writer = new XMLWriter(new FileOutputStream(filename),format);			
			writer.write(document);	
			writer.flush();
			writer.close();			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	public static void main(String[] args) {
		 getDocument();

	}

}
           
import java.sql.Date;

/**创建一个程序中实体对应xml中的信息,更加人性化,用户也更容易
 * 理解 当然这就需要xml中的东西与实体间进行转换,包含对象、操作等 
 * 1.创建一个用户类User,对应xml中的Student,用户的属性对应xml中Student所有子元素
 * 2.实体的各属性都要有set、get方法,以便设置、获取属性值
 * 3.尤其是对xml的文档的元素进行排序,如果用xml里面的元素排,在排的时候要把元素从他们的父节点上移除
 *   添加的时候还要设置被被添加的子元素的父节点为null,很是麻烦,用把Element转换成为User对象,排好了序
 *   再写到原来的xml文档,这样就简单很多。
 * 4.重写toString方法便于查看属性
 */
public class User {
	private Integer id = null;
	private String username =  null;
	private String password = null;
	private Date   birth = null;
	private String address = null;
	private String gender = null;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	
	@Override
	public String toString(){
		return "id=" + id + "\tusername=" + username + "\tpassword=" + password
				+ "\tgender="+gender+"\taddress"+address+"\tbirth="+birth;
	}
}


import java.util.List;

/**创建一个接口,让人一步了然的看清有什么方法,做了写什么事,
 * 还可以实现接口
 */
public interface IUserDAO {
	public List<User> getAllUser();
	public User getUserByUsername(String username);
	public List<User> getUserListByUsername(String username);
	public List<User> getUserListByKeyword1(String keyword);
	public List<User> getUserListByKeyword2(String keyword);
	public boolean addUser(User user);
	public boolean addUserWithUniquePK(User user)throws UserExistsException;
	public boolean addUserWithAutoPK(User user);
	public boolean deleteUserByPK(int id);
	public boolean update(User user);
	public void sortUserList(List<User> userList);
	public void sortUserList(List<User> userList,boolean ascendable);
	public void testXPath1();
	public User getUserById(int id);
	public void sortById1();
	public void sortById2();
	public void sortById3();
	public void sortById4();
}
           
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

public class UserDAO implements IUserDAO {
	/**
	 * 把一个Element对象转换成一个User对象 
	 * 1.得到Element各子元素存储的信息
	 * 2.把这些String类型的信息转换成相应的类型后赋值给User对象的属性
	 * 3.返回User对象
	 */
	private User getObjectFromElement(Element userElement) {
		String idStr = userElement.elementTextTrim("id");
		String username = userElement.elementTextTrim("username");
		String birthStr = userElement.elementTextTrim("birth");
		String gender = userElement.elementTextTrim("gender");
		String password = userElement.elementTextTrim("password");
		String address = userElement.elementTextTrim("address");

		Integer id = Integer.parseInt(idStr);
		// convert a string in the format "yyyy-mm-dd" into a Date type.
		Date birth = Date.valueOf(birthStr);
		User user = new User();
		user.setId(id);
		user.setUsername(username);
		user.setPassword(password);
		user.setBirth(birth);
		user.setAddress(address);
		user.setGender(gender);

		return user;
	}

	// 通过指定的用户名得到xml中第一个与指定用户名相同的用户对应的User对象
	public User getUserByUsername(String username) {
		try {
			Document document = DocumentManager.getDocument();
			Element usersElement = document.getRootElement();
			List<Element> userElementList = usersElement.elements();
			for (Element userElement : userElementList) {
				Element usernameElement = userElement.element("username");
				String _username = usernameElement.getText();
				if (username.equals(_username) == false) {
					continue;
				}
				return this.getObjectFromElement(userElement);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public List<User> getAllUser() {
		return null;
	}

	/**
	 * 通过指定的字符串查找到xml文件中用户名中包含此字符串的所有User对象
	 * 1.得到xml对应的Docuement对象,寻找到xml中所有的Student元素 
	 * 2.创建一个存储User对象的List列表
	 * 3.让Student的子元素也就是叶子节点存储的信息与我们想找的username比较
	 * 4.把匹配的转换成为User对象后添加到List中
	 * 5.返回这个List
	 */
	@Override
	public List<User> getUserListByUsername(String username) {
		Document document = DocumentManager.getDocument();
		Element usersElement = document.getRootElement();
		List<Element> userElementList = usersElement.elements();
		List<User> userList = new ArrayList<User>();
		// 创建一个ArrayList,这样的数组列表有序、可以重复、长度可变,
		// 用于存储我们找到的User对象
		for (Element userElement : userElementList) {
			String _username = userElement.elementTextTrim("username");
			if (_username.contains(username)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
			}
		}
		return userList;
	}

	/**
	 * 查询所有包含我们指定字符串内容信息的用户,并转换成User对象返回 
	 * 1.创建一个存储User对象的ArrayList
	 * 2.找到存有所有用户元素列表,循环找到各个user节点 
	 * 2.找到其字节点叶子节点,看他们的内容是否包含我们要查询的信息keywword
	 * 3.如果遇到任何一个叶子结点包含,就不再找这个user的后面元素了
	 * 4.满足条件的user节点转换成user对象,添加到List中
	 * 5.返回List
	 */
	public List<User> getUserListByKeyword1(String keyword) {
		Document document = DocumentManager.getDocument();
		Element usersElement = document.getRootElement();
		List<Element> userElementList = usersElement.elements();

		List<User> userList = new ArrayList<User>();
		for (Element userElement : userElementList) {
			String _username = userElement.elementTextTrim("username");
			if (_username.contains(keyword)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
				continue;
				// 如果这叶子节点包含我们想找的信息,就添加到List中,并终止本次循环
				// 不用在查这个user元素后面的子元素是否包含keyword
			}
			String password = userElement.elementTextTrim("password");
			if (password.contains(keyword)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
				continue;
			}
			String birth = userElement.elementTextTrim("birth");
			if (birth.contains(keyword)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
				continue;
			}
			String address = userElement.elementTextTrim("address");
			if (address.contains(keyword)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
				continue;
			}
			String gender = userElement.elementTextTrim("gender");
			if (gender.contains(keyword)) {
				User user = this.getObjectFromElement(userElement);
				userList.add(user);
				continue;
			}
		}
		return userList;
	}

	/**
	 * 与上面的getUserListByKeyword2功能一样
	 */
	public List<User> getUserListByKeyword2(String keyword) {
		Document document = DocumentManager.getDocument();
		Element usersElement = document.getRootElement();
		List<Element> userElementList = usersElement.elements();
		List<User> userList = new ArrayList<User>();
		for (Element userElement : userElementList) {
			List<Element> userSubelementList = userElement.elements();

			// traverse every sub-element of the user element.
			for (Element userSubelement : userSubelementList) {
				String elementValue = userSubelement.getTextTrim();
				if (elementValue.contains(keyword)) {
					// if the sub-element is located successfully,other
					// sub-element is not read any longer.
					User user = this.getObjectFromElement(userElement);
					userList.add(user);
					break;
					// 如果找到了user的某个子元素包含keyword,就跳出访问其
					// 子元素的这个循环,接着外层查找下个user的子节点是否包含
				}
			}
		}
		return userList;
	}

	/**
	 * 把一个User对象user,把它转换成为Element类型的user节点
	 * 1.创建名为user的Element对象
	 * 2.通过Element的addElement方法给user添加子元素 
	 * 3.设置子元素中的内容为User对象对应属性的内容
	 * 4.返回一个Element元素
	 */
	private Element getElementFromObject(User user) {
		Element userElement = DocumentHelper.createElement("user");
		// dom4j中的大多对象都不是构造方法生成的,而是用DcumentHelper来creat的
		// 包括文档、元素、属性、文本等都可以依次来生成
		userElement.addElement("id").setText(user.getId() + "");
		userElement.addElement("username").setText(user.getUsername());
		userElement.addElement("password").setText(user.getPassword());
		userElement.addElement("birth").setText(user.getBirth() + "");
		userElement.addElement("address").setText(user.getAddress());
		userElement.addElement("gender").setText(user.getGender());
		return userElement;
	}

	/**
	 * 添加User对象的用户user到XML文档中 
	 * 1.把User对象user转换成为Element类型的元素
	 * 2.得到指定xml文档的Document对象,找到要添加元素的节点 
	 * 3.通过addElement方法把要添加的元素填进去
	 * 4.更改计算user节点元素个数的count属性,每添加一个就加1 
	 * 5.把我们修改后的Document对象写到磁盘
	 */
	@Override
	public boolean addUser(User user) {
		Element userElement = this.getElementFromObject(user);
		Document document = DocumentManager.getDocument();
		Element usersElement = document.getRootElement();
		usersElement.add(userElement);
		this.setCountAttribute(usersElement, true);
		DocumentManager.writeDocument(document);
		return false;
	}

	/**
	 * 设置usersElement的计user数的属性count 
	 * 1.通过attributeValue方法得到该元素的指定属性的值 
	 * 2.转换成为整数类型
	 * 3.increment为false表示是增加元素,count就增加,反之减少
	 * 4.把count转换成字符串后通过addAtributte方法把设定好的属性添加到原节点中
	 */
	private void setCountAttribute(Element usersElement, boolean increment) {String countStr = usersElement.attributeValue("count");int count = 0;try {count = Integer.parseInt(countStr);} catch (NumberFormatException e) {
	// TODO Auto-generated 
	catch blocke.printStackTrace();}if (increment) {count++;} else {count--;}
	//addAttribute方法可以向元素中添加属性
	usersElement.addAttribute("count", count + "");
	}

	/**
	 * 添加id唯一的用户,一旦id相同就抛出异常。 
	 * 1.得到元素id 
	 * 2.坚持要插入元素的id是否已经存在 
	 * 3.如果不存在就添加元素,如果存在就抛出异常
	 * 这写操作是在转换成Element前做的,只是对User对象,这样更简便 *
	 */
	@Overridepublic
	boolean addUserWithUniquePK(User user) throws UserExistsException {
		Document document = DocumentManager.getDocument();
		if (this.findUserElementById(document, user.getId()) != null) {
			throw new UserExistsException("The user DOES EXIST!");
		}
		this.addUser(user);
		return false;
	}

	@Overridepublic
	boolean addUserWithAutoPK(User user) {
		// PK是primary key的缩写,主键的意思,这里就是id号
		// 自动设置用户id,找出现有id号最大的,让新加入的用户id号比最大的大一就可以了
		Document document = DocumentManager.getDocument();
		Element usersElement = document.getRootElement();
		List<Element> userElementList = usersElement.elements();
		int maxId = 0;
		for (Element userElement : userElementList) {
			String idStr = userElement.elementTextTrim("id");
			int id = 0;
			try {
				id = Integer.parseInt(idStr);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}
			if (maxId < id) {
				maxId = id;
			}
		}
		maxId++;
		user.setId(maxId);
		this.addUser(user);
		return false;
	}

	/** 通过id找到一个Document对象中的元素 */
	private Element findUserElementById(Document document, int id) {
		Element usersElement = document.getRootElement();
		List<Element> userElementList = usersElement.elements();
		for (Element userElement : userElementList) {
			// Step 2:
			String idStr = userElement.elementTextTrim("id");
			if (idStr.equals(id + "")) {
				return userElement;
			}
		}
		return null;
	}

	/**
	 * 删除指定id的节点  
	 * 1.通过id找到要删除的节点
	 *  2.找到要删除节点的父节点,可以用父接口中的getParent找到,在这里就是根节点
	 * 3.判断要删除的节点是否为空 
	 * 4.通过父节点,调用remove方法删除要删除的节点 
	 * 5.如果删除成功,就让计算user节点个数的减少
	 * 6.把修改过的Document对象写到原xml文件中 *
	 */
	public boolean deleteUserByPK(int id) {
		Document document = DocumentManager.getDocument();
		Element userElement = this.findUserElementById(document, id);
		if (userElement != null) {
			Element usersElement = document.getRootElement();
			if (usersElement.remove(userElement)) {
				// 执行remove方法的同时也做了判断条件,移除后才能设置计数的属性
				// alter the count attribute if succeeded in removing the
				element.this.setCountAttribute(usersElement, false);
			}
			// Step 4:re-write the document into the
			disc.DocumentManager.writeDocument(document);
		}
		return false;
	}

	/**
	 * 用户传入一个User对象,更新xml文档中对应元素的信息 
	 * 1.得到要更新的xml文档的document
	 * 2.由于id是唯一的,通过User对象中的id属性值,找到xml文档中对应id的元素
	 * 3.得到这个元素的父节点,可以用getParent方法,这里是根节点 
	 * 4.通过父节点调用remove方法移除原来的节点
	 * 5.把User对象转换成Element对象,添加到document中 
	 * 6.写更新后的document到xml文档
	 */
	@Overridepublic
	boolean update(User user) {
		Document document = DocumentManager.getDocument();
		Element userElement = this.findUserElementById(document, user.getId());
		Element usersElement = document.getRootElement();
		if (usersElement.remove(userElement)) {
			userElement = this.getElementFromObject(user);
			usersElement.add(userElement);
		}
		DocumentManager.writeDocument(document);
		return false;
	}

	/**
	 * 对一个存储User对象的UserList中的元素排序 
	 * 1.构建比较器对象,指定排序标准 
	 * 2.用Conections的sort方法排序
	 */
	@Overridepublic
	void sortUserList(List<User> userList, boolean ascendable) {
		// Step 1:make a sort
		standard.Comparator<User> userComparator = new UserComparator(
				ascendable);
		// Step 2:relate the sort standard to the sorted objects
		// 调用Collections的静态方法sort根据排序标准排序
		Collections.sort(userList, userComparator);
	}

	@Overridepublic
	void sortUserList(List<User> userList) {
		this.sortUserList(userList, true);
		// 默认排序顺序是升序
	}

	/**
	 * 通过xpath来快速查找 
	 * 1.设置xpath路径,是字符串的形式,XPath技术可以通过子元素名、自己属性名来获得节点, 
	 *    并可以进而找到其父节点、子节点、元素等
	 * 2.可以把xpath字符串路径传递给selectSingleNode、selectNodes方法,这两个方法来自Node,
	 * 来得到Node实例调用这两个方法的对象是说指XPath路径的父节点
	 */
	@Overridepublic
	void testXPath1() {
		// xpath可以根据条件(某个节点或者属性)快速定位某个节点。
		// locate user node whose sub-element id value is 121
		String xpath = "/users/user[id='121']";
		
		// 通过id值查找节点
		userDocument document = DocumentManager.getDocument();
		Node userElement = document.selectSingleNode(xpath);
		
		// selectSingleNode方法来自Node,可以通过xpath字符串路径得到一个Node实例
		// 此方法在此处返回的实例是Element对象,拥有子节点id为121的user节点
		System.out.println(this.getObjectFromElement((Element) userElement));
		System.out.println("username="
				+ ((Element) userElement).elementText("username"));
		String xpath = "/users/user[id='121']/username";
		Document document = DocumentManager.getDocument();
		Node userElement = document.selectSingleNode(xpath);
		
		// 此时selectSingleNode得到的是一个user节点的子元素username,
		// 该user节点有一个元素id,他的值为121
		System.out.println("username=" + ((Element) userElement).getTextTrim());
		String xpath = "/users/user[@firstname='liu']/birth";
		
		// 通过属性访问,这里的selectSingleNode方法得到的是user的一个子元素birth,
		// 这个user有个属性叫firstname,属性值为liu
		Element birthElement = (Element) DocumentManager.getDocument()
				.getRootElement().selectSingleNode(xpath);	
	}

	/**
	 * 通过id得到用户User对象
	 *  1.通过指定指定id建立可变的XPath字符串路径 
	 *  2.通过XPath路径得到xml文档中对应的元素
	 *  3.把找到的元素转换成为User对象类型返回
	 */
	@Overridepublic
	User getUserById(int id) {
		String xpath = "/users/user[id='" + id + "']";
		System.out.println("xpath=" + xpath);
		Element userElement = (Element) DocumentManager.getDocument()
				.selectSingleNode(xpath);
		return this.getObjectFromElement(userElement);
	}

	/**
	 * 让xml文档按照id进行排序 * 保留document,建新的根节点users 
	 * 1.得到原xml文档的document 
	 * 2.创建一个List
	 * 3.取原来xml文档的所有等待排序的user元素存在List中
	 * 4.把所有要排序的user元素从原根节点中移除来,这样他们才“自由”了,才可以进行排序,否则会抛出异常 
	 * 5.对List中所有元素排序 *
	 * 6.设置List中所有元素的父节点为null 
	 * 7.新建一个Element对象users
	 * 8.把排好序的List中的元素依次添加到users下面,添加一个节点为子元素时,必须确认其父元素为空
	 * 9.在document下面移除原来的根节点,设置users为根节点 
	 * 10.写document到xml文档
	 */
	public void sortById3() {
		List<Element> userElementList = rootElement.elements("user");
		// 把原来的所有用户存入List中
		Element newRootElement = DocumentHelper.createElement("users");
		
		// System.out.println("newRootElement父节点为"+newRootElement.getParent());
		// 重新创建一个users的节点
		for (Element userElement : userElementList) {
			rootElement.remove(userElement);
		}
		
		// 把原来根节点的所有内容从原来的根节点移除出去,这样这些节点才是自由的,才可以排序?
		List l = rootElement.elements();
		System.out
				.println("rootElement.elements() 有 " + rootElement.elements());
		Comparator userElementComparator = new UserElementComparator();
		Collections.sort(userElementList, userElementComparator);
		
		// sort方法根据Comparator中的compare方法的结果进行排序,Comparator是个比较器。
		// 此实现将指定列表转储到一个数组中,并对数组进行排序,在重置数组中相应位置每个元素的列表上进行迭代。
		for (Element userElement : userElementList) {
			System.out.println(userElement.elementTextTrim("id"));
		}
		for (Element userElement : userElementList) {
			// System.out.println("userElement父节点是:"+userElement.getParent());
			userElement.setParent(null);
			// System.out.println("userElement父节点变成了:"+userElement.getParent());
			// System.out.println();
			// 设置userElement的父节点为空newRootElement.add(userElement);
			// 在新的根节点上添加元素}newRootElement.setAttributeValue("count",
			// userElementList.size() + "");
			// 为新的根节点添加属性document.setRootElement(newRootElement);
			// setRootElement可以设置document的根节点DocumentManager.writeDocument(document);}}
		}
	}
}