使用DOM方式操作XML文件,即是和DOM树打交道的过程:在构建XML文件时,首先构建一棵DOM树,然后将该树状结构写成XML文件;在解析XML文件时,首先将源XML文件解析成一棵DOM树,然后遍历这棵DOM树、或从DOM树中查找需要的信息。
关于DOM树中节点类型、不同节点具有的接口、特性、限制等信息可以参考《DOM树节点解析》,本文只关注如何构建XML文件与解析XML文件。在构建和解析XML文件中,都以w3school中的books.xml文件的内容为例:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<bookcategory="web"cover="paperback" >
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
<book category="web">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
</bookstore>
我们都知道Java是一门面向对象的语言,因而我们需要尽量以面向对象的思想我编写代码,面向对象编程其中一个比较重要的特点就是基于对象编程,因而我们在编写这个测试代码时,也尽量的基于对象操作,而不是像过程式的语言,有一点信息做一点操作。
在这里,对XML文件中定义的book元素,我们使用Book对象与其对应:
public class Book {
private String category;
private String cover;
private TitleInfo title;
private List<String> authors;
private int year;
private double price;
...
public static class TitleInfo {
private String title;
private String lang;
...
}
}
根据XML文件定义构建Book实例:
public class W3CBooksBuilder {
public static List<Book> buildBooks() {
List<Book> books = new ArrayList<Book>();
books.add(buildHarrayBook());
books.add(builcEverydayItalian());
books.add(buildLearningXML());
books.add(buildXQueryKickStart());
return books;
}
public static Book buildHarrayBook() {
Book book = new Book();
book.setCategory("children");
book.setTitle(new TitleInfo("Harry Potter", "en"));
book.setAuthors(Arrays.asList("J K. Rowling"));
book.setYear(2005);
book.setPrice(29.99);
return book;
}
public static Book builcEverydayItalian() {
...
}
public static Book buildLearningXML() {
...
}
public static Book buildXQueryKickStart() {
...
}
}
DOM解析XML文件
DOM使用DocumentBuilder类来解析XML文件,它提供parse方法,将XML文件解析成一棵DOM树,并返回Document实例:
public Document parse(InputStream is);
public Document parse(InputStream is, String systemId);
public Document parse(String uri);
public Document parse(File f);
public abstract Document parse(InputSource is);
DocumentBuilder类还提供了判断当前解析器是否存在命名空间解析、验证等配置,以及提供了设置EntityResolver、ErrorHandler的接口。这里使用EntityResolver和ErrorHandler只是重用SAX的API,并不表示DOM解析的内部实现一定要基于SAX,然而貌似JDK自带的DOM解析内部使用的引擎就是SAX。T_T
public abstract boolean isNamespaceAware();
public abstract boolean isValidating();
public abstract void setEntityResolver(EntityResolver er);
public abstract void setErrorHandler(ErrorHandler eh);
DocumentBuilder提供了 构建Document实例的工厂方法,在以编程方式构建DOM树时,首先需要构建Document实例,继而使用Document实例构建其余节点类型,而构建Document实例需要通过DocumentBuilder类来实现:
public abstract Document newDocument();
最后,DocumentBuilder还提供了一些额外的方法,比如重置DocumentBuilder实例的状态,以重用该DocumentBuilder;获取DOMImplementation实例;获取Schema实例;判断XInclude处理模式。
public void reset();
public abstract DOMImplementation getDOMImplementation();
public Schema getSchema();
public boolean isXIncludeAware();
DocumentBuilder是一个抽象类,要获取DocumentBuilder实例,需要使用DocumentBuilderFactory。DocumentBuilderFactory提供了多种查找DocumentBuilder实现类的方法;DocumentBuilderFactory本身也是抽象类,它提供了两个静态方法来创建DocumentBuilderFactory实例:
public static DocumentBuilderFactory newInstance();
public static DocumentBuilderFactory newInstance(String factoryClassName, ClassLoader classLoader);
不带参数的newInstance()方法使用以下步骤查找DocumentBuilderFactory的实现类:
1. 查看系统属性中是否存在javax.xml.parsers.DocumentBuilderFactory为key的定义,如果存在,则使用该key定义的值作为DocumentBuilderFactory的实现类。
2. 查找${java.home}/lib/jaxp.properties属性文件中是否存在javax.xml.parsers.DocumentBuilderFactory为key的定义,若存在,则使用该属性文件中以该key定义的值作为DocumentBuilderFactory的实现类。
3. 查找当前ClassPath(包括jar包中)下是否存在META-INF/services//javax.xml.parsers.DocumentBuilderFactory文件的定义(ServiceProvider),若存在,则读取该文件中的第一行的值作为DocumentBuilderFactory的实现类。
4. 若以上都没有找到,则使用默认的DocumentBuilderFactory的实现类:
com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
在找到相应的DocumentBuilderFactory实现类后,实例化该实现类,并返回DocumentBuilderFatory实例。这里的查找机制和XMLReaderFactory查找XMLReader实现类以及commons-logging查找LogFactory的机制很像。
对带参数的newInstance()方法,直接使用参数中提供的DocumentBuilderFactory实现类以及ClassLoader来创建DocumentBuilderFactory实例。
最后,在系统属性中将jaxp.debug设置为true可以打开调试信息。
在创建DocumentBuilderFactory实例后,如其名所示,它可以用于获取DocumentBuilder实例,另外,DocumentBuilderFactory还提供了配置解析器的方法:
public abstract DocumentBuilder newDocumentBuilder();
public void setNamespaceAware(boolean awareness);
public boolean isNamespaceAware();
public void setValidating(boolean validating);
public boolean isValidating();
public void setIgnoringElementContentWhitespace(boolean whitespace);
public boolean isIgnoringElementContentWhitespace();
public void setExpandEntityReferences(boolean expandEntityRef);
public boolean isExpandEntityReferences();
public void setIgnoringComments(boolean ignoreComments);
public boolean isIgnoringComments();
public void setCoalescing(boolean coalescing);
public boolean isCoalescing();
public void setXIncludeAware(final boolean state);
public boolean isXIncludeAware();
public abstract void setAttribute(String name, Object value);
public abstract Object getAttribute(String name);
public abstract void setFeature(String name, boolean value);
public abstract boolean getFeature(String name);
public Schema getSchema();
public void setSchema(Schema schema);
在创建出DocumentBuilderFactory,使用该factory创建DocumentBuilder实例后,就可以使用该DocumentBuilder解析XML文件成一个Document实例,而通过该Document实例就可以遍历、查找DOM树,从而获得想要的信息。在下面的例子中,遍历DOM树,创建多个Book实例:
public class W3CBooksDOMReader {
private static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
private String booksXmlFile;
public W3CBooksDOMReader(String booksXmlFile) {
this.booksXmlFile = booksXmlFile;
}
public List<Book> parse() {
Document doc = parseXmlFile();
Element root = doc.getDocumentElement();
NodeList nodes = root.getElementsByTagName("book");
List<Book> books = new ArrayList<Book>();
for(int i = 0; i < nodes.getLength(); i++) {
books.add(parseBookElement((Element)nodes.item(i)));
}
return books;
}
private Document parseXmlFile() {
File xmlFile = new File(booksXmlFile);
if(!xmlFile.exists()) {
throw new RuntimeException("Cannot find xml file: " + booksXmlFile);
}
try {
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(xmlFile);
} catch(Exception ex) {
throw new RuntimeException("Failed to create DocumentBuilder instance", ex);
}
}
private Book parseBookElement(Element bookElement) {
String category = bookElement.getAttribute("category");
String cover = bookElement.getAttribute("cover");
NodeList nodes = bookElement.getElementsByTagName("title");
String lang = ((Element)nodes.item(0)).getAttribute("lang");
// First way to get content of an Element
String title = ((Text)((Element)nodes.item(0)).getFirstChild()).getData().trim();
List<String> authors = new ArrayList<String>();
nodes = bookElement.getElementsByTagName("author");
for(int i = 0; i < nodes.getLength(); i++) {
// Second way to get content of an Element
String author = nodes.item(0).getTextContent().trim();
authors.add(author);
}
nodes = bookElement.getElementsByTagName("year");
int year = Integer.parseInt(nodes.item(0).getTextContent().trim());
nodes = bookElement.getElementsByTagName("price");
double price = Double.parseDouble(nodes.item(0).getTextContent().trim());
Book book = new Book();
book.setCategory(category);
book.setCover(cover);
book.setTitle(new TitleInfo(title, lang));
book.setAuthors(authors);
book.setYear(year);
book.setPrice(price);
return book;
}
public String getBooksXmlFile() {
return booksXmlFile;
}
public static void main(String[] args) {
W3CBooksDOMReader reader = new W3CBooksDOMReader("resources/xmlfiles/w3c_books.xml");
List<Book> books = reader.parse();
System.out.println("result:");
for(Book book : books) {
System.out.println(book);
}
}
}
DOM构建XML文件
将对象实例序列化成XML文件,首先需要构建DOM树,即要构建Document实例,然后将该Document实例写入的XML文件中。如上节所述,可以使用DocumentBuilder类来创建Document实例,然后根据对象实例(Book实例)和需要的XML格式构建节点和节点的排布即可,这里不再详述。
要将对象序列化成XML文件还要处理的另一个问题是如何将Document实例写入到指定的XML文件中,在Java中提供了Transformer接口来做这件事情。这属于XLST(EXtensible Stylesheet Language)的范畴,不过这里不打算对其做详细介绍,主要关注如何将Document实例输出成XML文件。
Transformer提供了transform方法将Document实例写入指定的流中:
public abstract void transform(Source xmlSource, Result outputTarget);
其中Source接口定义了输入源,它可以是DOMSource,也可以是SAXSource,或者是自定义的其他Source子类,这里主要介绍DOMSource。Source接口定义了systemId属性,它表示XML源的位置,XML源不是从URL中获取的源来说,它为null。具体定义如下:
public interface Source {
public void setSystemId(String systemId);
public String getSystemId();
}
DOMSource是对Source的一个具体实现,它接收Node、systemId信息:
public class DOMSource implements Source {
private Node node;
private String systemID;
public DOMSource() { }
public DOMSource(Node n) {
setNode(n);
}
public DOMSource(Node node, String systemID) {
setNode(node);
setSystemId(systemID);
}
...
}
Result是对输出目的的抽象,即将输入源转换成目的源。同Source接口,Result接口也定义了systemId属性,表示目的文件位置,如果目的源不是URL,则改值为null:
public interface Result {
public void setSystemId(String systemId);
public String getSystemId();
}
JDK中提供了多种Result的实现,如DOMResult、StreamResult等。这里只介绍StreamResult,表示其输出目的是流,我们可以提供Writer、OutputStream等实例来接收这些输出:
public class StreamResult implements Result {
public StreamResult() {
}
public StreamResult(OutputStream outputStream) {
setOutputStream(outputStream);
}
public StreamResult(Writer writer) {
setWriter(writer);
}
public StreamResult(String systemId) {
this.systemId = systemId;
}
public StreamResult(File f) {
setSystemId(f.toURI().toASCIIString());
}
...
private String systemId;
private OutputStream outputStream;
private Writer writer;
}
除了transform方法,Transformer类还提供了其他的方法用于配置Transformer在转换时用到的信息(只提供接口定义,不详述):
public void reset();
public abstract void setParameter(String name, Object value);
public abstract Object getParameter(String name);
public abstract void clearParameters();
public abstract void setURIResolver(URIResolver resolver);
public abstract URIResolver getURIResolver();
public abstract void setOutputProperties(Properties oformat);
public abstract Properties getOutputProperties();
public abstract void setOutputProperty(String name, String value);
public abstract String getOutputProperty(String name);
public abstract void setErrorListener(ErrorListener listener);
public abstract ErrorListener getErrorListener();
类似DocumentBuilder,Transformer通过TransformerFactory创建,而TransformerFactory的创建如同DocumentBuilderFactory的创建以及查找机制,所不同的是TransformerFactory的属性名为:javax.xml.transform.TransformerFactory,其默认实现类为:com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl,而且它也提供了两个获取TransformerFactory实例的方法,这里不再详述:
public static TransformerFactory newInstance();
public static TransformerFactory newInstance(String factoryClassName, ClassLoader classLoader);
TransformerFactory提供了创建Transformer和Templates的方法,同时也提供了在创建这两个实例时可以设置的一些配置方法:
public abstract Transformer newTransformer(Source source);
public abstract Transformer newTransformer();
public abstract Templates newTemplates(Source source);
public abstract Source getAssociatedStylesheet(Source source, String media,
String title, String charset);
public abstract void setURIResolver(URIResolver resolver);
public abstract URIResolver getURIResolver();
public abstract void setFeature(String name, boolean value);
public abstract boolean getFeature(String name);
public abstract void setAttribute(String name, Object value);
public abstract Object getAttribute(String name);
public abstract void setErrorListener(ErrorListener listener);
public abstract ErrorListener getErrorListener();
最后,提供一个完整的例子,使用本文开始时创建的List<Book>实例序列化成XML文件:
public class W3CBooksDOMWriter {
private static DocumentBuilder docBuilder;
private static Transformer transformer;
static {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
docBuilder = factory.newDocumentBuilder();
} catch(Exception ex) {
throw new RuntimeException("Create DocumentBuilder instance failed.", ex);
}
TransformerFactory transFactory = TransformerFactory.newInstance();
try {
transformer = transFactory.newTransformer();
} catch(Exception ex) {
throw new RuntimeException("Create Transformer instance failed.", ex);
}
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
}
private List<Book> books;
public W3CBooksDOMWriter(List<Book> books) {
this.books = books;
}
public void toXml(Writer writer) throws Exception {
Document doc = buildDOMTree();
writeToXmlFile(writer, doc);
}
public Document buildDOMTree() {
Document doc = docBuilder.newDocument();
Element root = doc.createElement("bookstore");
doc.appendChild(root);
for(Book book : books) {
Element bookElement = buildBookElement(doc, book);
root.appendChild(bookElement);
}
return doc;
}
public Element buildBookElement(Document doc, Book book) {
Element bookElement = doc.createElement("book");
bookElement.setAttribute("category", book.getCategory());
bookElement.setAttribute("cover", book.getCover());
TitleInfo title = book.getTitle();
Element titleElement = doc.createElement("title");
titleElement.setAttribute("lang", title.getLang());
titleElement.setTextContent(title.getTitle());
bookElement.appendChild(titleElement);
for(String author : book.getAuthors()) {
Element authorElement = doc.createElement("author");
authorElement.setTextContent(author);
bookElement.appendChild(authorElement);
}
Element yearElement = doc.createElement("year");
yearElement.setTextContent(String.valueOf(book.getYear()));
bookElement.appendChild(yearElement);
Element priceElement = doc.createElement("price");
priceElement.setTextContent(String.valueOf(book.getPrice()));
bookElement.appendChild(priceElement);
return bookElement;
}
public void writeToXmlFile(Writer writer, Document doc) throws Exception {
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
}
public static void main(String[] args) throws Exception {
StringWriter writer = new StringWriter();
List<Book> books = W3CBooksBuilder.buildBooks();
W3CBooksDOMWriter domWriter = new W3CBooksDOMWriter(books);
domWriter.toXml(writer);
System.out.println(writer.toString());
}
}