上一篇讲了Java解析XML,然而如果仅仅按照这种方法来操作,会发现需要大量冗长的编程和错误检查工作。不但需要处理元素间的空白字符,还要检查该文档包含的节点是否和你预期一样。所以在解析XML之前需要验证XML文档的正确性。
场景
例如,读入下面这个XML文档,请看Java XML文档解析
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book id="1">
<name>Java 核心技术</name>
<author>Cornell </author>
<year>2014</year>
<price>89</price>
</book>
<book id="2">
<name>深入浅出MyBatis</name>
<author>杨开振</author>
<year>2016</year>
<price>69</price>
</book>
<book id="3">
<name>Java RESTful Web Service实战</name>
<author>韩陆</author>
<year>2016</year>
<price>59</price>
</book>
</bookstore>
如果不进行XML验证,会出现如下情况
- 解析的时候需要判断两个节点中的空白字符,如果是空白字符就不读取,不是就读取。
- 同时如果多写了一个属性<category>computer</category>到book节点。如果没有验证,那么解析的时候就会将category作为一个节点读入,这可能会导致错误。
<book id="3">
<name>Java RESTful Web Service实战</name>
<author>韩陆</author>
<year>2016</year>
<price>59</price>
<category>computer</category>
</book>
- 如果没对book id进行限制,那么可能存在两个book id相等,这个是不允许发生的
XML解析器
XML解析器最大好处就是它能自动检验某个文档是否具有正确的结构。可以提供文档类型定义(DTD)或一个XML Schema定义。DTD包含用于解释文档如何构成的规则,这些规则指定每个元素的合法子元素和属性。
DTD
-book.dtd 用来描述上述XML的构成的规则,DTD语法规则,参考链接
<?xml version="1.0" encoding="UTF-8"?>
<!--bookstore元素,包括多个book子元素 -->
<!ELEMENT bookstore (book)*>
<!--book元素,包括name,author,year,price子元素-->
<!ELEMENT book (name,author,year,price)>
<!--book元素,有一个属性名为id的属性,这个属性是必须的 -->
<!ATTLIST book id ID #REQUIRED>
<!--name元素,元素只包括文本 -->
<!ELEMENT name (#PCDATA)>
<!--author元素,元素只包括文本 -->
<!ELEMENT author (#PCDATA)>
<!--author元素,元素只包括文本 -->
<!ELEMENT year (#PCDATA)>
<!--price元素,元素只包括文本 -->
<!ELEMENT price (#PCDATA)>
将上述book.dtd纳入入到xml文件中。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bookstore SYSTEM "book.dtd">
<bookstore>
<book id="1">
<name>Java 核心技术</name>
<author>Cornell </author>
<year>2014</year>
<price>89</price>
</book>
<book id="2">
<name>深入浅出MyBatis</name>
<author>杨开振</author>
<year>2016</year>
<price>69</price>
</book>
<book id="3">
<name>Java RESTful Web Service实战</name>
<author>韩陆</author>
<year>2016</year>
<price>59</price>
</book>
</bookstore>
在DOM解析时候可以通过下列代码来进行验证
dBuilderFactory = DocumentBuilderFactory.newInstance();
//解析的时候开启验证
dBuilderFactory.setValidating(true);
//忽略节点之间的空白字符
dBuilderFactory.setIgnoringElementContentWhitespace(true);
dBuilder = dBuilderFactory.newDocumentBuilder();
整体代码如下
public class ReadXMLByDOMWithValidating {
private static DocumentBuilderFactory dBuilderFactory = null;
private static DocumentBuilder dBuilder = null;
static {
try {
/**
* 要读入一个XML文档,首先要有一个DocumentBuilder对象 可以从DocumentBuilderFactory中得到这个对象
*/
dBuilderFactory = DocumentBuilderFactory.newInstance();
dBuilderFactory.setValidating(true);
dBuilderFactory.setIgnoringElementContentWhitespace(true);
dBuilder = dBuilderFactory.newDocumentBuilder();
dBuilder.setErrorHandler(new ErrorHandler() {
public void warning(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
// TODO Auto-generated method stub
throw exception;
}
public void error(SAXParseException exception) throws SAXException {
// TODO Auto-generated method stub
throw exception;
}
});
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static List<Book> listBooks(String filename) throws SAXException, IOException {
List<Book> books = new ArrayList<Book>();
// 可通过DocumentBuilder对象的parse()方法读入整个文档
Document document = dBuilder.parse(filename);
// 获得根节点,books.xml对应的就是bookstore节点
Element root = document.getDocumentElement();
// 输出根节点的名字,bookstore
System.out.println(root.getTagName());
// 获得所有的book节点
NodeList children = root.getChildNodes();
// 循环遍历各个book节点
for (int i = 0; i < children.getLength(); i++) {
// 获得第i个book节点
Node child = children.item(i);
// 用来存储第i个节点的内容
List<String> bookAttrbuteContent = new ArrayList<String>();
Book book = new Book();
/**
* 这里要注意的是dom会把两个节点之间的空白字符也当做节点 要判断是否是子元素, 而不是空白字符,这个可以参照 《Java核心技术卷
* 二》的解析XML文档章节,有详细的解释
*/
Element childElement = (Element) child;
int bookId = Integer.parseInt(childElement.getAttribute("id").replace("book", ""));
System.out.println(bookId);
book.setId(bookId);
NodeList bookAttrbuteList = childElement.getChildNodes();
// 循环遍历book节点的各个子节点,如name,author...
for (int j = 0; j < bookAttrbuteList.getLength(); j++) {
Node bookAttrbute = bookAttrbuteList.item(j);
String content = bookAttrbute.getTextContent().trim();
System.out.println(((Element) bookAttrbute).getTagName() + ":" + content);
bookAttrbuteContent.add(content);
}
book.setName(bookAttrbuteContent.get(0));
book.setAuthor(bookAttrbuteContent.get(1));
book.setYear(Integer.parseInt(bookAttrbuteContent.get(2)));
book.setPrice(Integer.parseInt(bookAttrbuteContent.get(3)));
books.add(book);
}
return books;
}
public static void main(String args[]) {
String fileName = "./src/main/java/com/gethin/xmlparser/bookstore.xml";
try {
List<Book> books = ReadXMLByDOMWithValidating.listBooks(fileName);
for (Book book : books) {
System.out.println(book);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}