1. Java XML编程概述
在这里,主要是通过给出一些例子,来理清SAX,DOM,JDOM,JXAP四者之间的关系。首先,SAX和DOM是最基础的解释器接口。而JDOM是在模型上统一了SAX和DOM,使得SAX和DOM处理的结果都为一个JDocument。JAXP则是在API上统一了各SAX和各DOM,他的作用有点类似于JDBC,隐藏了底层采用的具体的SAX或DOM解释器,而提供统一API编程接口。
2. XML文件
1.xml(用于下文显示)
<?xml version="1.0" encoding="gb2312"?>
<statistic>
<header>
<column name="Cache大小"/>
<column name="Cache命中"/>
<column name="时间(ms)"/>
</header>
<data>
<x value="10">
<y value="493"/>
<y value="1281"/>
</x>
<x value="11">
<y value="560"/>
<y value="1063"/>
</x>
</data>
</statistic>
3. SAX 2.0
a) 模型:
XMLReader |
ContentHandler |
DTDHandler |
ErrorHandler |
b) 模型说明:
SAX的实现模型采用了Builder的设计模式。XMLReader负责XML文件的读取和识别,并且根据文本的识别结果,产生对应的事件通知,随后将这些事件发送到注册在XMLReader上的各种Handler。这样做的好处是系统只需要一个单独的XMLReader负责完成XML文件的解释,而具体这些被解释的内容做何种用途,则由用户所实现的Handler来定义。
c) 代码:
MyContentHandler的实现代码:
package sax;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class MyHandler extends DefaultHandler {
private int indent = 0;
public void startElement(String uri, String localName, String qName,
Attributes attrs) {
emit();
System.out.print("<" + localName);
int count = attrs.getLength();
for (int i = 0; i < count; i++) {
System.out.print(" " + attrs.getLocalName(i) + "=/""
+ attrs.getValue(i) + "/"");
}
System.out.println(">");
indent++;
}
public void endElement(String uri, String localName, String qName) {
indent--;
emit();
System.out.println("</" + localName + ">");
}
public void characters(char[] ch, int start, int length) {
String text = String.valueOf(ch, start, length);
if (!("".equals(text.trim())))
System.out.println();
}
private void emit() {
for (int i = 0; i < indent; i++)
System.out.print("/t");
}
}
测试代码:
package sax;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Test {
public static void main(String[] args) {
try {
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setContentHandler(new MyHandler());
reader.parse(new InputSource(new FileReader("1.xml")));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
<statistic>
<header>
<column name="Cache大小">
</column>
<column name="Cache命中">
</column>
<column name="时间(ms)">
</column>
</header>
<data>
<x value="10">
<y value="493">
</y>
<y value="1281">
</y>
</x>
<x value="11">
<y value="560">
</y>
<y value="1063">
</y>
</x>
</data>
</statistic>
注:MyHandler的作用是一个简单的XML文件显示器。她输出XML文件中的所有元素标签,以及元素中的属性和文本。
d) XMLFilter
顾名思义,XMLFilter主要用于对XML文件的内容进行过滤。
i. 模型:
event |
event |
event |
parse() |
parse() |
XMLReader |
XMLFilter1 |
XMLFilter2 |
ContentHandler |
ii. 模型说明:
XMLFilter模型的实现主要是采用了责任连的模式。XMLFitler扩展了XMLReader接口,而XMLFilter的实现类XMLFilterImpl实现了ContentHandler,DTDHandler等接口,因此XMLFilterImpl可以作为普通的Handler注册到XMLReader上。另外XMLFilterImpl的构造函数中,她通过接受一个XMLReader实例作为她的父引用,从而可以把多个XMLFilter串成一个责任链,进行多级过滤。
iii. 代码:
MyFilter的实现代码:
package sax;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class MyFilter extends XMLFilterImpl {
public MyFilter(XMLReader reader) {
super(reader);
}
public void startElement(String uri, String localName, String qName,
Attributes atts) {
try {
if ("data".equals(qName)) {
return;
} else {
super.startElement(uri, localName, qName, atts);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void endElement(String uri, String localName, String qName) {
try {
if ("data".equals(qName)) {
return;
} else {
super.endElement(uri, localName, qName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试代码:
package sax;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Test {
public static void main(String[] args) {
try {
XMLReader reader = XMLReaderFactory.createXMLReader();
MyFilter filter = new MyFilter(reader);
filter.setContentHandler(new MyHandler());
filter.parse(new InputSource(new FileReader("1.xml")));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
<statistic>
<header>
<column name="Cache大小">
</column>
<column name="Cache命中">
</column>
<column name="时间(ms)">
</column>
</header>
<x value="10">
<y value="493">
</y>
<y value="1281">
</y>
</x>
<x value="11">
<y value="560">
</y>
<y value="1063">
</y>
</x>
</statistic>
注:MyFilter的作用就是过滤调名称为data的标签,不在结果中输出。
iv. 注意事项:
由于XMLFilter本身并不具备解释XML文档的能力,因此在构造Filter Chain时,在最高层的必须是XMLReader实例。
e) Handler介绍:
除了常用的ContentHandler,DTDHandler等Handler之外。还有以下几个属于SAX 2.0的,新的Handler。
i. LexicalHandler
用于处理由DTD,注释,CDATA字段触发的事件。
ii. DeclHandler
用于处理DTD中的ELEMENT和ATTRIBUTE定义所触发的事件。
f) Feature和Properties
通过setProperty()和getProperty()可以对XMLReader上的属性进行操作。通过setFeature()和getFeature()可以对XMLReader上的特征进行操作。主要是检查Validate,Namespace等方面的属性。Feature和Properties是解释器相关的,因此具体能设置哪些Feature和Properties需要查看解释器的实现文档。
4. DOM 1.0
a) 模型:
DOMParser |
Document |
b) 模型说明:
DOM的实现模型与SAX的最主要区别是,用户并不是直接在XML文件上操作。而是由DOM解释器先对文件进行解释,然后生成符合该XML文件结构的一棵DOM树,最后用户程序再与这棵DOM树进行交互。
c) 代码:
DOM程序:
/*
* Created on 2004-12-24
*
*/
package dom;
import org.w 3c .dom.*;
import javax.xml.parsers.*;
import java.io.*;
import org.apache.xerces.parsers.*;
import org.xml.sax.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Test {
private static int indent = 0;
public static void main(String[] args) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new FileReader("1.xml")));
Document doc = parser.getDocument();
output(doc);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void output(Node node) {
int type = node.getNodeType();
if (type == Node.DOCUMENT_NODE) {
int length;
NodeList childs = node.getChildNodes();
length = childs.getLength();
for (int i = 0; i < length; i++) {
output(childs.item(i));
}
} else if (type == Node.TEXT_NODE) {
String value = node.getNodeValue();
if (!"".equals(value.trim())) {
emit();
System.out.println(value);
}
} else if (type == Node.ELEMENT_NODE) {
emit();
System.out.print("<" + node.getNodeName());
NamedNodeMap attrs = node.getAttributes();
int length = attrs.getLength();
for (int i = 0; i < length; i++) {
System.out.print(" " + attrs.item(i).getNodeName() + "="
+ attrs.item(i).getNodeValue());
}
System.out.println(">");
indent++;
NodeList childs = node.getChildNodes();
length = childs.getLength();
for (int i = 0; i < length; i++) {
output(childs.item(i));
}
indent--;
emit();
System.out.println("</" + node.getNodeName() + ">");
}
}
public static void emit() {
for (int i = 0; i < indent; i++)
System.out.print("/t");
}
}
输出:
<statistic>
<header>
<column name="Cache大小">
</column>
<column name="Cache命中">
</column>
<column name="时间(ms)">
</column>
</header>
<x value="10">
<y value="493">
</y>
<y value="1281">
</y>
</x>
<x value="11">
<y value="560">
</y>
<y value="1063">
</y>
</x>
</statistic>
注:这个DOM程序的作用就是输出XML文件所对应的DOM树的主要节点。书写DOM程序的重点就在于如何进行对树的遍历,这里最重要的编程技巧就是如何书写递归程序。由于我们采用的时apache的xerces解释器,因此上面的DOM程序是使用实现相关的方式。
d) 创建树
这里主要演示如何通过程序的方式创建一颗DOM树。以下的程序使用了apache的xerces解释器。
创建树的程序:
/*
* Created on 2004-12-26
*
*/
package dom;
import org.w 3c .dom.*;
import org.apache.xerces.dom.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Create {
public static void main(String[] args) {
DOMImplementation impl = new DOMImplementationImpl();
Document doc = impl.createDocument(null, "item", null);
Element e = doc.createElement("name");
Text t = doc.createTextNode("Hello");
Element root = doc.getDocumentElement();
root.setAttribute("id", "nick");
e.appendChild(t);
root.appendChild(e);
Test.output(doc);
}
}
输出:
<item id=nick>
<name>
Hello
</name>
</item>
注:在使用程序创建DOM树时。我们可以注意到,我们需要先获得一个DOMImplementationImpl实例,然后在她之上,我们就可以得到DOM树的Document对象,然后在Document之上,我们可以得到所有我们所需要的元素,文本节点,而这也是整个程序所要反映的关键所在。
e) 修改树:
在这里主要演示,怎么通过程序的方式来修改DOM树的内容和结构。
修改树的程序:
/*
* Created on 2004-12-24
*
*/
package dom;
import org.w 3c .dom.*;
import javax.xml.parsers.*;
import java.io.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Modify {
public static void main(String[] args) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("1.xml"));
Test.output(doc);
Element root = doc.getDocumentElement();
NodeList list = root.getElementsByTagName("data");
Element e = (Element) list.item(0);
/* 修改节点内容 */
Text t = (Text) e.getFirstChild();
t.setData("Hello World");
/* 修改树的结构 */
NodeList headlist = root.getElementsByTagName("header");
for (int i = 0; i < headlist.getLength(); i++) {
root.removeChild(headlist.item(i));
}
Test.output(doc);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
之前:
<statistic>
<header>
<column name=Cache大小>
</column>
<column name=Cache命中>
</column>
<column name=时间(ms)>
</column>
</header>
<data>
<x value=10>
<y value=493>
</y>
<y value=1281>
</y>
</x>
<x value=11>
<y value=560>
</y>
<y value=1063>
</y>
</x>
</data>
</statistic>
之后:
<statistic>
<data>
Hello World
<x value=10>
<y value=493>
</y>
<y value=1281>
</y>
</x>
<x value=11>
<y value=560>
</y>
<y value=1063>
</y>
</x>
</data>
</statistic>
注:修改树的程序与创建树的程序的区别就在于,我们在修改树的程序中,主要要做的就是先要通过Document实例获得DOM树的DocumentElement,即DOM树的根节点。然后在这以后,我们就可以通过遍历或者查找得到我们要的节点引用,并进行相关操作。这里有一点特别要注意的是,在调用removeChild操作时,两节点之间必须是父子关系。从上述程序当中,我们可以发现DOM 1.0在修改树时,需要非常了解树的整体结构。
5. DOM 2.0
DOM 2.0 为DOM树上的节点操作,提供了一套更新,更灵活的API。
a) Traversal
Traversal是属于DOM 2.0的一个新功能,他的作用主要就是可以对节点进行遍历。但是由于J2SE并没有提供对Traversal的支持,因此这里只能使用解释器相关的操作。
程序代码:
/*
* Created on 2004-12-27
*
*/
package dom;
import java.io.*;
import org.xml.sax.*;
import org.w 3c .dom.*;
import org.apache.xerces.parsers.*;
import org.apache.xerces.dom.*;
import org.w 3c .dom.traversal.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Traversal {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new FileReader("1.xml")));
Document doc = parser.getDocument();
Element root = doc.getDocumentElement();
NodeIterator i = ((DocumentTraversal) doc).createNodeIterator(root,
NodeFilter.SHOW_ALL, null, true);
Node node = null;
while ((node = i.nextNode()) != null) {
System.out.println(node.getNodeType() + " "
+ node.getNodeName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
1 statistic
3 #text
1 header
3 #text
1 column
3 #text
1 column
3 #text
1 column
3 #text
3 #text
1 data
3 #text
1 x
3 #text
1 y
3 #text
1 y
3 #text
3 #text
1 x
3 #text
1 y
3 #text
1 y
3 #text
3 #text
3 #text
注:NodeIterator的作用与Iterator同,里面的元素均是Node,程序可直接在这些Node引用上进行操作。其中createNodeIterator()的第一个参数是开始进行搜索的根位置,第二个是系统预定义的过滤器,第三个是用户自定义的过滤器。其中,用户自定义的过滤器要在系统定义的过滤器之后起作用。第四个是“是否需要展开实体引用”。
DOM 1.0中我们可以通过getElementByName来获取某一名称的节点列表;而在DOM 2.0中我们可以通过Traversal接口达到同样的效果。而Traversal的优点就在于,他的过滤能力比1.0中的getElement要强。因为,在自定义的过滤器当中,我们可以实现很复杂的过滤逻辑,而不仅仅是对名称等有限的,单一的属性过滤。
b) 带过滤的Traversal
过滤器:
/*
* Created on 2004-12-27
*
*/
package dom;
import org.w 3c .dom.traversal.*;
import org.w 3c .dom.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class TraversalFilter implements NodeFilter {
public short acceptNode(Node node) {
if ("header".equals(node.getNodeName().trim()))
return NodeFilter.FILTER_REJECT;
else if (node.getNodeType() == Node.ELEMENT_NODE) {
return NodeFilter.FILTER_ACCEPT;
} else
return NodeFilter.FILTER_SKIP;
}
}
程序代码:
/*
* Created on 2004-12-27
*
*/
package dom;
import java.io.*;
import org.xml.sax.*;
import org.w 3c .dom.*;
import org.apache.xerces.parsers.*;
import org.apache.xerces.dom.*;
import org.w 3c .dom.traversal.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Traversal {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new FileReader("1.xml")));
Document doc = parser.getDocument();
Element root = doc.getDocumentElement();
NodeIterator i = ((DocumentTraversal) doc).createNodeIterator(root,
NodeFilter.SHOW_ALL, new TraversalFilter(), true);
Node node = null;
while ((node = i.nextNode()) != null) {
System.out.println(node.getNodeType() + " "
+ node.getNodeName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
1 statistic
1 column
1 column
1 column
1 data
1 x
1 y
1 y
1 x
1 y
1 y
注:NodeFilter只有一个acceptNode()的方法,她的返回值类型是一个short,她的值只有三个NodeFilter.FILTER_SKIP,表示只跳过当前节点,NodeFilter.FILTER_REJECT,表示跳过当前节点及其所有子孙节点,NodeFilter.FILTER_ACCEPT,表示接受当前节点。上述的用户自定义的Filter演示了这三个值的作用,对于名称为header的节点,我们把她的整个子树过滤掉,对于节点类型不是Node.ELEMENT_NODE的节点,我们只是把该节点过滤掉。从上述程序中我们可以看到,通过用户实现方法,和返回特定的short值,我们可以比DOM 1.0提供更加复杂的过滤逻辑。
c) TreeWalker
TreeWalker也是属于DOM 2.0的一个新功能,他的作用也是对节点进行遍历。但是由于J2SE并没有提供对TreeWalker的支持,因此这里只能使用解释器相关的操作。
程序代码:
/*
* Created on 2004-12-27
*
*/
package dom;
import java.io.FileReader;
import org.apache.xerces.parsers.DOMParser;
import org.w 3c .dom.*;
import org.w 3c .dom.traversal.*;
import org.xml.sax.InputSource;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Walker {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new FileReader("1.xml")));
Document doc = parser.getDocument();
Element root = doc.getDocumentElement();
TreeWalker tw = ((DocumentTraversal) doc).createTreeWalker(root,
NodeFilter.SHOW_ELEMENT, null, true);
Node node = null;
while ((node = tw.nextNode()) != null) {
System.out.println(node.getNodeType() + " "
+ node.getNodeName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
1 header
1 column
1 column
1 column
1 data
1 x
1 y
1 y
1 x
1 y
1 y
注:在创建TreeWalker实例时调用的createTreeWalker()方法的四个参数的含义与createNodeIterator()同。他们之间的区别是NodeIterator是节点的线性列表。而TreeWalker则是按照树型组织节点。
d) Range
Range也是DOM 2.0提供的一个新功能。他可以通过程序的方式,截取出DOM树中的某一段,然后对该段进行一些特定的操作。
程序代码:
/*
* Created on 2004-12-27
*
*/
package dom;
import java.io.FileReader;
import org.apache.xerces.parsers.DOMParser;
import org.w 3c .dom.*;
import org.xml.sax.InputSource;
import org.w 3c .dom.ranges.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class MyRange {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new FileReader("1.xml")));
Document doc = parser.getDocument();
Element data = (Element) doc.getElementsByTagName("data").item(0);
Range range = ((DocumentRange) doc).createRange();
range.setStartBefore(data.getFirstChild());
range.setEndAfter(data.getLastChild());
range.deleteContents();
range.detach();
Test.output(doc);
} catch (Exception e) {
}
}
}
输出:
<statistic>
<header>
<column name=Cache大小>
</column>
<column name=Cache命中>
</column>
<column name=时间(ms)>
</column>
</header>
<data>
</data>
</statistic>
注:上述程序的作用,就是把data的子孙节点设置成一个Range,然后把这个Range整个进行删除。
6. JDOM
JDOM的设计目标,主要就是在模型上对SAX和DOM进行统一。
a) 模型:
DOMBuilder b = new DOMBuilder(); Document d = b.build(…); |
SAXBuilder b = new SAXBuilder(); Document d = b.build(…); |
SAX Builder |
DOM Builder |
JDOM Document |
XML Outputter |
SAX Outputter |
DOM Outputter |
XML Document |
DOM Tree
|
b) 模型说明:
从上图可以看到,JDOM主要是通过SAX和DOM的Builder把XML文件或者已经建立好的DOM树,转化成JDOM的内部Document形式,然后再利用其他的Outputter对这个内部的Document进行输出。
c) 代码:
程序代码:
/*
* Created on 2004-12-27
*
*/
package jdom;
import java.io.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Test {
public static void main(String[] args) {
try {
SAXBuilder b = new SAXBuilder();
Document d = b.build(new File("1.xml"));
XMLOutputter o = new XMLOutputter();
Format f = Format.getPrettyFormat();
f.setEncoding("gb2312");
o.setFormat(f);
o.output(d, System.out);
} catch (Exception e) {}
}
}
输出:
<?xml version="1.0" encoding="gb2312"?>
<statistic>
<header>
<column name="Cache大小" />
<column name="Cache命中" />
<column name="时间(ms)" />
</header>
<data>
<x value="10">
<y value="493" />
<y value="1281" />
</x>
<x value="11">
<y value="560" />
<y value="1063" />
</x>
</data>
</statistic>
注:当使用XMLOutputter时,我们需要提供一个输出流,当使用SAXOutputter时,我们需要设定对应的ContentHandler,当使用DOMOutputter时,我们将得到Document节点。所以,总的来说,我认为JDOM的意义更多的是在SAX,DOM和流之间的转换。
7. JAXP
正如本文开始部分所说,JAXP的意义就在于他提供一套统一的API,而不管底层使用的是哪个厂商提供的解释器,因此,JAXP的意义与JDBC类似。
a) SAX解释器:
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(Input,ContentHandler);
b) DOM解释器:
DocumentBuilderFacotry factory = DocumentBuilderFacotry.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(Input);
8. XSLT
作为本文的最后部分,这里主要就是介绍一下如何在Java程序中结合XML和XSL文件。
a) 代码:
程序代码:
/*
* Created on 2004-12-27
*
*/
package xsl;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import java.io.*;
import org.w 3c .dom.*;
/**
*
* @author cenyongh@mails.gscas.ac.cn
*/
public class Test {
public static void main(String[] args) {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
Document doc = builder.parse(new File("1.xml"));
DOMSource sor = new DOMSource(doc);
StreamResult res = new StreamResult(System.out);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer t = factory.newTransformer();
t.transform(sor, res);
} catch (Exception e) {}
}
}
注:通过与JDOM比较,我们可以看到。当我们不使用XSL文件时,我们也可以很容易的通过Transformer提供的方法,实现从SAX,DOM和流之间的转换。如果我们需要引入XSL文件的话,那么在获取新的Transformer实例时,作为参数传递到newTransformer()方法中。