Day06 DOM4J&schema介绍&xPath
day06总结
今日内容
- XML解析之JAXP( SAX )
- DOM4J
- Schema
三、XML解析器介绍
操作XML文档概述
1 如何操作XML文档
XML文档也是数据的一种,对数据的操作也不外乎是"增删改查"。也被大家称之为"CRUD"。
- C:Create;
- R:Retrieve;
- U:Update;
- D:Delete
2 XML解析技术
XML解析方式分为两种:DOM(Document Object Model)和SAX(Simple API for XML)。这两种方式不是针对Java语言来解析XML的技术,而是跨语言的解析方式。例如DOM还在Javascript中存在!
DOM是W3C组织提供的解析XML文档的标准接口,而SAX是社区讨论的产物,是一种事实上的标准。
DOM和SAX只是定义了一些接口,以及某些接口的缺省实现,而这个缺省实现只是用空方法来实现接口。一个应用程序如果需要DOM或SAX来访问XML文档,还需要一个实现了DOM或SAX的解析器,也就是说这个解析器需要实现DOM或SAX中定义的接口。提供DOM或SAX中定义的功能。
解析原理
1 DOM解析原理
使用DOM要求解析器把整个XML文档装载到一个Document对象中。Document对象包含文档元素,即根元素,根元素包含N多个子元素…
一个XML文档解析后对应一个Document对象,这说明使用DOM解析XML文档方便使用,因为元素与元素之间还保存着结构关系。
优先:使用DOM,XML文档的结构在内存中依然清晰。元素与元素之间的关系保留了下来!
缺点:如果XML文档过大,那么把整个XML文档装载进内存,可能会出现内存溢出的现象!
2 设置Java最大内存
运行Java程序,指定初始内存大小,以及最大内存大小。
java -Xms20m -Xmx100m MyClass
3 SAX解析原理
DOM会一行一行的读取XML文档,最终会把XML文档所有数据存放到Document对象中。SAX也是一行一行的读取XML文档,但是当XML文档读取结束后,SAX不会保存任何数据,同时整个解析XML文档的工作也就结束了。
但是,SAX在读取一行XML文档数据后,就会给感兴趣的用户一个通知!例如当SAX读取到一个元素的开始时,会通知用户当前解析到一个元素的开始标签。而用户可以在整个解析的过程中完成自己的业务逻辑,当SAX解析结束,不会保存任何XML文档的数据。
优先:使用SAX,不会占用大量内存来保存XML文档数据,效率也高。
缺点:当解析到一个元素时,上一个元素的信息已经丢弃,也就是说没有保存元素与元素之间的结构关系,这也大大限制了SAX的使用范围。如果只是想查询XML文档中的数据,那么使用SAX是最佳选择!
解析器概述
1 什么是XML解析器
DOM、SAX都是一组解析XML文档的规范,其实就是接口,这说明需要有实现者能使用,而解析器就是对DOM、SAX的实现了。一般解析器都会实现DOM、SAX两个规范!
- Crimson(sun):JDK1.4之前,Java使用的解析器。性能效差,可以忘记它了!
- Xerces(IBM):IBM开发的DOM、SAX解析器,现在已经由Apache基金会维护。是当前最为流行的解析器之一!在1.5之后,已经添加到JDK之中,也是JAXP的默认使用解析器,但不过在JDK中的包名与Xerces不太一样。例如:org.apache.xerces包名改为了com.sun.org.apache.xerces.internal包名,也就是说JDK1.5中的Xerces是被包装后的XML解析器,但二者区别很小。
JAXP概述
1 什么是JAXP
JAXP是由Java提供的,用于隐藏底层解析器的实现。Java要求XML解析器去实现JAXP提供的接口,这样可以让用户使用解析器时不依赖特定的XML解析器。
JAXP本身不是解析器(不是Xerces),也不是解析方式(DOM或SAX),它只是让用户在使用DOM或SAX解析器时不依赖特点的解析器。
当用户使用JAXP提供的方式来解析XML文档时,用户无需编写与特定解析器相关的代码,而是由JAXP通过特定的方式去查找解析器,来解析XML文档。
2 JAXP对DOM的支持
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse("src/students.xml"); |
那么newInstance()这个方法又是如果找到解析器提供商的工厂类的呢?此方法使用下面有序的查找过程来确定要加载的DocumentBuilderFactory实现类:
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "工厂实现类名字");
我们不建议大家用上面的方法来硬编码这个系统属性的值,如果这样设置,假如将来需要更换解析器,就必需修改代码。
javax.xml.parsers.DocumentBuilderFactory=工厂实现类名字
这个key名字必须是javax.xml.parsers.DocumentBuilderFactory,而相对应的值也必须设置类路径。
四、如果说在前三种途径中都没有找到工厂实现类,那么就使用平台缺省工厂实现类。
在获取到某个特定解析器厂商的DocumentBuilderFactory后,那么这个工厂对象创建出来的解析器对象当然就是自己厂商的解析器对象了。
3 JAXP对SAX的支持
SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse("src/students.xml", new DefaultHandler() { public void startDocument() throws SAXException { System.out.println("解析开始"); }
public void endDocument() throws SAXException { System.out.println("解析结束"); }
public void processingInstruction(String target, String data) throws SAXException { System.out.println("处理指令"); }
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { System.out.println("元素开始:" + qName); }
public void characters(char[] ch, int start, int length) throws SAXException { System.out.println("文本内容:" + new String(ch, start, length).trim()); }
public void endElement(String uri, String localName, String qName) throws SAXException { System.out.println("元素结束:" + qName); } }); |
JAXP对SAX的支持与JAXP对DOM的支持是相同的,这里就不在赘述!
JDOM和DOM4J
1 JDOM和DOM4J概述
2 JDOM和DOM4J比较
JDOM与DOM4J相比,DOM4J完胜!!!所以,我们应该在今后的开发中,把DOM4J视为首选。
在2000年,JDOM开发过程中,因为团队建议不同,分离出一支队伍,开发了DOM4J。DOM4J要比JDOM更加全面。
3 DOM4J查找解析器的过程
DOM4J首先会去通过JAXP的查找方法去查找解析器,如果找到解析器,那么就使用之;否则会使用自己的默认解析器Aelfred2。
DOM4J对DOM和SAX都提供了支持,可以把DOM解析后的Document对象转换成DOM4J的Document对象,当然了可以把DOM4J的Document对象转换成DOM的Document对象。
DOM4J使用SAX解析器把XML文档加载到内存,生成DOM对象。当然也支持事件驱动的方式来解析XML文档。
XML解析之JAXP(DOM)
JAXP获取解析器
1 JAXP相关包
2 JAXP与DOM、SAX解析器的关系
JAXP只是作用只是为了让使用者不依赖某一特定DOM、SAX的解析器实现,当使用JAXP API时,使用者直接接触的就是JAXP API,而不用接触DOM、SAX的解析器实现API。
3 JAXP获取DOM解析器
当我们需要解析XML文档时,首先需要通过JAXP API解析XML文档,获取Document对象。然后用户就需要使用DOM API来操作Document对象了。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse("src/students.xml"); |
4 JAXP保存Document
当我们希望把Document保存到文件中去时,可以使用Transformer对象的transform()方法来完成。想获取Transformer对象,需要使用TransformerFactory对象。
与JAXP获取DOM解析器一样,隐藏了底层解析器的实现。也是通过抽象工厂来完成的,这里就不在赘述了。
TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); trans.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "students.dtd"); trans.setOutputProperty(OutputKeys.INDENT, "yes"); Source source = new DOMSource(doc); Result result = new StreamResult(xmlName); transformer.transform(source, result); |
Transformer类的transform()方法的两个参数类型为:Source和Result,DOMSource是Source的实现类,StreamResult是Result的实现类。
5 JAXP创建Document
有时我们需要创建一个Document对象,而不是从XML文档解析而来。这需要使用DocumentBuider对象的newDocument()方法。
DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.newDocument(); doc.setXmlVersion("1.0"); doc.setXmlStandalone(true); |
5 学习DOM之前,先写两个方法
DOM API概述
1 Document对应XML文档
无论使用什么DOM解析器,最终用户都需要获取到Document对象,一个Document对象对应整个XML文档。也可以这样说,Document对象就是XML文档在内存中的表示形式。
通常我们最为"关心"的就是文档的根元素。所以我们必须要把Document获取根元素的方法记住:Element getDocumentElement()。然后通过根元素再一步步获取XML文档中的数据。
2 DOM API中的类
3 Node方法介绍
Node获取子节点和父节点方法,只有Document和Element才能使用这些方法:
Node获取弟兄节点的方法,只有Element才能使用这些方法:
Node添加、替换、删除子节点方法
:- Node appendChild(Node newChild):把参数节点newChild添加到当前节点的子节点列表的末尾处。返回值为被添加的子节点newChild对象,方便使用链式操作。如果newChild在添加之前已经在文档中存在,那么就是修改节点的位置了;
- Node insertBefore(Node newChild, Node refNode):把参数节点newChild添加到当前节点的子节点refNode之前。返回值为被添加的子节点newChild对象,方便使用链式操作。如果refNode为null,那么本方法与appendNode()方法功能相同。如果newChild节点在添加之前已经在文档中存在,那么就是修改节点的位置了。
- Node removeNode(Node oldChild):从当前节点中移除子元素oldChild。返回值为被添加的子节点oldChild对象,方便使用链式操作。
- Node replaceNode(Node newChild, Node oldChild):将当前节点的子节点oldChild替换为newChild。
Node获取属性集合方法,只有Element可以使用:
- NamedNodeMap getAttributes():返回当前节点的属性集合。NamedNodeMap表示属性的集合,方法如下:
- int getLength():获取集合中属性的个数;
- Node item(int index):获取指定下标位置上的属性节点;
- Node getNamedItem(String name):获取指定名字的属性节点;
- Node removeNamedItem(String name):移除指定名字的属性节点,返回值为移除的属性节点;
- Node setNamedItem(Node arg):添加一个属性节点,返回值为添加的属性节点。
Node的判断方法:
- boolean hasChildNodes():判断当前节点是否有子节点;
- boolean hasAttribute():判断当前节点是否有属性。
4 Docment方法介绍
创建节点方法:
- Attr createAttribute(String name):创建属性节点;
- CDATASection createCDATASection(String data):创建CDATA段节点;
- Comment createComment(String data):创建注释;
- Element createElement(String tagName):创建元素节点;
- Text createTextNode(String data):创建文本节点;
获取子元素方法:
5 Element方法介绍
6 Attr方法介绍
SAX
SAX概述
1 SAX解析原理
2 获取SAX解析器
与DOM相同,你应该通过JAXP获取SAX解析器,而不是直接使用特定厂商的SAX解析器。JAXP查找特定厂商的SAX解析器实现的方式与查找DOM解析器实现的方式完全相同,这里就不在赘述了。
SAXParserFactory factory = SAXParserFactory.newInstance();
javax.xml.parsers.SAXParser parser = factory.newSAXParser();
parser.parse("src/students.xml", new MyContentHandler());
上面代码中,MyContentHandler就是我们自己需要编写的ContentHandler的实现类对象。
3 内容处理器
org.xml.sax.ContentHandler中的方法:
- void setDocumentLocator(Locator locator):与定位相关,例如获取行数、实体、标识等信息,我们可以忽略他的存在;
org.xml.sax.helpers.DefualtHandler对ContentHandler做了空实现,所以我们可以自定义内容处理器时可以继承DefaultHandler类。
SAX应用
public class SAXTest { @Test public void testSAX() throws ParserConfigurationException, SAXException, IOException { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse("src/students.xml", new MyContentHandler()); } private static class MyContentHandler extends DefaultHandler { @Override public void startDocument() throws SAXException { System.out.println("开始解析..."); } @Override public void endDocument() throws SAXException { System.out.println("解析结束..."); } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { System.out.println(qName + "元素解析开始"); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { System.out.println(qName + "元素解析结束"); } @Override public void characters(char[] ch, int start, int length) throws SAXException { String s = new String(ch, start, length); if(s.trim().isEmpty()) { return; } System.out.println("文本内容:" + s); } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
} @Override public void processingInstruction(String target, String data) throws SAXException { System.out.println("处理指令"); } } } |
2 使用SAX打印XML文档
public class SAXTest2 { @Test public void testSAX() throws ParserConfigurationException, SAXException, IOException { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse("src/students.xml", new MyContentHandler()); } private static class MyContentHandler extends DefaultHandler { @Override public void startDocument() throws SAXException { System.out.println("<?xml version='1.0' encoding='utf-8'?>"); }
@Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { StringBuilder sb = new StringBuilder(); sb.append("<").append(qName); for(int i = 0; i < atts.getLength(); i++) { sb.append(" "); sb.append(atts.getQName(i)); sb.append("="); sb.append("'"); sb.append(atts.getValue(i)); sb.append("'"); } sb.append(">"); System.out.print(sb); }
@Override public void endElement(String uri, String localName, String qName) throws SAXException { System.out.print("</" + qName + ">"); }
@Override public void characters(char[] ch, int start, int length) throws SAXException { System.out.print(new String(ch, start, length)); } } } |
DOM4J
DOM4J概述
1 DOM4J是什么
DOM4J是针对Java开发人员专门提供的XML文档解析规范,它不同与DOM,但与DOM相似。DOM4J针对Java开发人员而设计,所以对于Java开发人员来说,使用DOM4J要比使用DOM更加方便。
DOM4J对DOM和SAX提供了支持,使用DOM4J可以把org.dom4j.document转换成org.w3c.Document,DOM4J也支持基于SAX的事件驱动处理模式。
DOM4J使用JAXP来查找SAX解析器,然后把XML文档解析为org.dom4j.Document对象。它还支持使用org.w3c.Document来转换为org.dom4j.Docment对象。
2 DOM4J中的类结构
在DOM4J中,也有Node、Document、Element等接口,结构上与DOM中的接口比较相似。但还是有很多的区别:
在DOM4J中,所有XML组成部分都是一个Node,其中Branch表示可以包含子节点的节点,例如Document和Element都是可以有子节点的,它们都是Branch的子接口。
Attribute是属性节点,CharacterData是文本节点,文本节点有三个子接口,分别是CDATA、Text、Comment。
3 DOM4J获取Document对象
使用DOM4J来加载XML文档,需要先获取SAXReader对象,然后通过SAXReader对象的read()方法来加载XML文档:
SAXReader reader = new SAXReader(); // reader.setValidation(true); Document doc = reader.read("src/students.xml"); |
4 DOM4J保存Document对象
doc.addDocType("students", "", "students.dtd"); OutputFormat format = new OutputFormat("\t", true); format.setEncoding("UTF-8"); XMLWriter writer = new XMLWriter(new FileWriter(xmlName), format); writer.write(doc); writer.close(); |
5 DOM4J创建Document对象
DocumentHelper类有很多的createXXX()方法,用来创建各种Node对象。
Document doc = DocumentHelper.createDocument(); |
Document操作 (*****)
1 遍历students.xml
2 给学生元素添加<score>子元素
3 为张三添加friend属性,指定为李四学号
4 删除number为ID_1003的学生元素
5 通过List<Student>生成Document并保存
6 新建赵六学生元素,插入到李四之前
7 其它方法介绍
Schema
Schema概述
我们学习Schema的第一目标是:参照Schema的要求可以编写XML文档;
1 Schema是什么
DTD文档不是XML语法,而Schema本身也是XML文档,这对解析器来说不用再去处理非XML的文档了;
DTD只能表述平台线束,而Schema本身也是XML,所以可以描述结构化的约束信息。
DTD不只约束元素或属性的类型,但Schema可以。例如让age属性的取值在0~100之间。
Schema文档的扩展名为xsd,即XML Schema Definition。
<!ELEMENT students (student+)> <!ELEMENT student (name,age,sex)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> <!ELEMENT sex (#PCDATA)> <!ATTLIST student number CDATA #REQUIRED> |
3 为students.xml编写schema
<?xml version="1.0"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="students" type="studentsType"/> <xsd:complexType name="studentsType"> <xsd:sequence> <xsd:element name="student" type="studentType" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="studentType"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="age"> <xsd:simpleType> <xsd:restriction base="xsd:integer"> <xsd:maxInclusive value="100"/> <xsd:minInclusive value="0"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="sex"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="男"/> <xsd:enumeration value="女"/> </xsd:restriction> </xsd:simpleType> </xsd:element> </xsd:sequence> <xsd:attribute name="number" type="xsd:string"/> </xsd:complexType> </xsd:schema> |
参照Schema编写XML文档
我们参照上面的Schema文档编写一个studens.xml文件
<?xml version="1.0" encoding="utf-8" standalone="no" ?> |
名称空间相关内容
1 什么是名称空间
名称空间是用来处理XML元素或属性的名字冲突问题。你可以理解为Java中的包!包的作用就是用来处理类的名字冲突问题。
我们在下面讲解XML名称空间时,会使用Java包的概念来理解XML的名称空间的概念,所以现在大家就要注意这么几个特性:
2 声明名称空间(导包)
名称空间命名:一般名称空间都是以公司的URL来命名,即网址!当然也可以给名称空间命名为aa、bb之类的名字,但这可能会导致名称空间的重名问题。
3 默认名称空间
所谓默认名称空间就是在声明名称空间时,不指定前缀,也可以理解为前缀为空字符串的意思。这样定义元素时,如果没有指定前缀的元素都是在使用默认名称空间中的元素。
当在文档中使用<xxx>时,那么<xxx>元素就是http://www.itcast.cn名称空间中声明的元素。
注意:没有指定前缀的属性不表示在默认名称空间中,而是表示没有名称空间。也就是说,默认名称空间不会涉及到属性,只对元素有效!
XPath(扩展)
XPath概述
1 什么是XPath
2 DOM4J对XPath的支持
** 一个xml中可以有多个schema,多个schema使用名称空间区分(类似于java包名)
** dtd里面有PCDATA类型,但是在schema里面可以支持更多的数据类型
*** 比如 年龄 只能是整数,在schema可以直接定义一个整数类型
*** schema语法更加复杂,schema目前不能替代dtd
** 属性 xmlns="http://www.w3.org/2001/XMLSchema"
** targetNamespace="http://www.itcast.cn/20151111"
** elementFormDefault="qualified"
<element name="name" type="string"></element>
<element name="age" type="int"></element>
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.itcast.cn/20151111"
xsi:schemaLocation="http://www.itcast.cn/20151111 1.xsd">
** xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
** xmlns="http://www.itcast.cn/20151111"
** xsi:schemaLocation="http://www.itcast.cn/20151111 1.xsd">
-- targetNamespace 空格 约束文档的地址路径
maxOccurs="unbounded": 表示元素的出现的次数
<attribute name="id1" type="int" use="required"></attribute>
<company xmlns = "http://www.example.org/company"
xmlns:dept="http://www.example.org/department"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
* 想要引入部门的约束文件里面的name,使用部门的别名 detp:元素名称
此类的实例可以从 SAXParserFactory.newSAXParser() 方法获得
- parse(File f, DefaultHandler dh)
* 当解析到开始标签时候,自动执行startElement方法
*** 执行parse方法,第一个参数xml路径,第二个参数是 事件处理器
** 判断开始方法是否是name元素,如果是name元素,把flag值设置成true
** 如果flag值是true,在characters方法里面打印内容
-- 判断flag=true 并且 idx==1,在打印内容
* dom4j,是一个组织,针对xml解析,提供解析器 dom4j
* dom4j不是javase的一部分,想要使用第一步需要怎么做?
-- 右键点击jar包,build path -- add to build path
SAXReader reader = new SAXReader();
Document document = reader.read(url);
* 如果在document里面找不到想要的方法,到Node里面去找
* document里面的方法 getRootElement() :获取根节点 返回的是Element
3、得到根节点 getRootElement() 返回Element
* 在p1下面执行 element("name")方法 返回Element
** element("name")方法 返回Element
** 使用list下标得到 get方法,集合的下标从 0 开始,想要得到第二个值,下标写 1
** element("name")方法 返回Element
* 在第一个p1标签末尾添加一个元素 <sex>nv</sex>
* 在p1上面直接使用 addElement("标签名称")方法 返回一个Element
* 格式化 OutputFormat,使用 createPrettyPrint方法,表示一个漂亮的格式
* 使用类XMLWriter 直接new 这个类 ,传递两个参数
*** 第一个参数是xml文件路径 new FileOutputStream("路径")
* 在第一个p1下面的age标签之前添加 <school>ecit.edu.cn</schlool>
- 使用DocumentHelper类方法createElement创建标签
- 把文本添加到标签下面 使用 setText("文本内容")方法
* *** list集合里面的 add(int index, E element)
** 可以对得到document的操作和 回写xml的操作,封装成方法
- 比如想要修改文件路径(名称),这个时候只需要修改常量的值就可以了,其他代码不需要做任何改变
* 修改第一个p1下面的age元素的值 <age>30</age>
* 删除第一个p1下面的<school>ecit</school>元素
/AAA/DDD/BBB: 表示一层一层的,AAA下面 DDD下面的BBB
//BBB: 表示选择所有该名称的元素,表示只要名称是BBB,就都可以选择到
** //BBB[@id]: 表示只要BBB元素上面有id属性,都得到
** //BBB[@id='b1'] 表示元素名称是BBB,在BBB上面有id属性,并且id的属性值是b1
* 第一步需要,引入支持xpath的jar包,使用 jaxen-1.1-beta-6.jar
*** selectSingleNode("xpath表达式")
* 2、直接使用selectNodes("//name")方法得到所有的name元素
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
//使用selectNodes("//name")方法得到所有的name元素
List<Node> list = document.selectNodes("//name");
* 使用到 selectSingleNode("//p1[@id1='aaaa']/name")
* - xpath : //p1[@id1='aaaa']/name
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
Node name1 = document.selectSingleNode("//p1[@id1='aaaa']/name"); //name的元素