摘要

本文记述了如何使用Java 8API 解析但不验证、按照XML文件头的DOCTYPE声明验证、使用本地文件验证XML的方法。本文不涉及如何读取、修改XML节点,以及创建XML文档的内容。

解析但不验证

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class XMLParser {
	public static void main(String[] args) {
		try {
			String xmlToParse = "myDocument.xml";
			DocumentBuilderFactory dbf = 
					DocumentBuilderFactory.newInstance();
			// 默认DocumentBuilderFactory不创建
			// 启用验证功能的DocumentBuilder
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document myDoc = db.parse(xmlToParse);
		} catch (ParseConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}

使用XML文件头部声明的DOCTYPE验证

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class XMLParser {
	public static void main(String[] args) {
		try {
			String xmlToParse = "myDocument.xml";
			DocumentBuilderFactory dbf = 
					DocumentBuilderFactory.newInstance();
			dbf.setValidating(true);  // 注意这里不同
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document myDoc = db.parse(xmlToParse);
		} catch (ParseConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}

这时可能抛出IOException,原因通常是没有找到XML所声明的DTD文件

  • 如果XML声明的DTD在本地,可能会报FileNotFoundException。此时需要检查本地DTD的路径是否填写正确
  • 否则可能报SocketException。此时需要检查网络是否畅通

然而此时即使XML不符合所声明DTD的定义,SAXException也可能不会被抛出,而仅仅是报错信息通过System.err打印出来,同时会打印运行警告:“警告: 已启用验证, 但未设置 org.xml.sax.ErrorHandler, 这可能不是预期结果。解析器将使用默认 ErrorHandler 来输出前 0 个错误。请调用 ‘setErrorHandler’ 方法以解决此问题。”

这是因为没有设置ErrorHandler。如果希望SAXException在发生验证错误时被抛出,需要通过DocumentBuilder.setErrorHandler(ErrorHandler eh)方法进行设置。

重写上述代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;  // 注意这里不同
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;  // 注意这里不同

public class XMLParser {
	public static void main(String[] args) {
		try {
			String xmlToParse = "myDocument.xml";
			DocumentBuilderFactory dbf = 
					DocumentBuilderFactory.newInstance();
			dbf.setValidating(true);
			DocumentBuilder db = dbf.newDocumentBuilder();
			db.setErrorHandler(new ErrorHandler() {
				/*
				 * 定义了一个只要出一点解析错误就抛出异常的ErrorHandler。
				 * 读者可以以此为依据编写更精细化管理的ErrorHandler。
				 */
				
				@Override
				public void error(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
				@Override
				public void fatalError(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
				@Override
				public void warning(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
			});  // 注意这里不同
			Document myDoc = db.parse(xmlToParse);
		} catch (ParseConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}

使用本地DTD文件验证

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.EntityHandler;  // 注意这里不同
import org.xml.sax.InputSource;  // 注意这里不同
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class XMLParser {
	public static void main(String[] args) {
		try {
			String xmlToParse = "myDocument.xml";
			DocumentBuilderFactory dbf = 
					DocumentBuilderFactory.newInstance();
			dbf.setValidating(true);
			DocumentBuilder db = dbf.newDocumentBuilder();
			db.setErrorHandler(new ErrorHandler() {
				@Override
				public void error(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
				@Override
				public void fatalError(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
				@Override
				public void warning(SAXParseException exception)
						throws SAXException {
					throw exception;
				}
			});
			db.setEntityResolver(new EntityResolver() {
				/*
				 * 编写了根据PUBLIC域使用相应的本地dtd的EntityResolver;
				 * 读者也可以据此编写根据SYSTEM域使用相应dtd的EntityResolver;
				 * 或不管xml中声明成什么DOCTYPE,都使用同一份dtd进行验证,
				 * 此时resolveEntity方法体中仅包含
				 *     return new InputSource("a-fixed-dtd-path");
				 */
				
				@Override
				public InputSource resolveEntity(String publicId,
						String systemId) {
					switch (publicId) {  // 此处仅为示意
					case "URL-sample-1":
						return new InputSource(
								"local-dtd-path-for-url-sample-1");
					case "URL-sample-2":
						return new InputSource(
								"local-dtd-path-for-url-sample-2");
					default:
						// 仍然按照DOCTYPE去解析,此时可能抛出IOException
						return null;
				}
			});  // 注意这里不同
			Document myDoc = db.parse(xmlToParse);
		} catch (ParseConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}