「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。
DOM解析XML文件
基本使用语法
company.xml
<?xml version="1.0" encoding="UTF-8"?>
<company name="公司">
<group name="软件部">
<leader name="张三" />
<staff name="李四" />
<staff name="王五" />
</group>
<group name="人事部">
<leader name="陈六" />
<staff name="赵七" />
<staff name="皇甫八" />
</group>
</company>
DomXmlDecode.java
import java.io.IOException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
public class DomDecode {
private static final String COMPANY_ELEMENT_TAG_NAME = "company";
private static final String GROUP_ELEMENT_TAG_NAME = "group";
private static final String LEADER_ELEMENT_TAG_NAME = "leader";
private static final String STAFF_ELEMENT_TAG_NAME = "staff";
public static void main(String[] args) {
try {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse("src/company.xml");
printNodes(doc.getChildNodes());
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
}
private static void printNodes(NodeList nodes) {
if (nodes.getLength() > 0) {
for (int index = 0, times = 0; index < nodes.getLength(); index++) {
Node node = nodes.item(index);
if (COMPANY_ELEMENT_TAG_NAME.equals(node.getNodeName())) {
System.out.println(node.getNodeName());
} else if (GROUP_ELEMENT_TAG_NAME.equals(node.getNodeName())) {
times++;
System.out.println("\t" + node.getNodeName() + " " + times);
} else if (LEADER_ELEMENT_TAG_NAME.equals(node.getNodeName()) || STAFF_ELEMENT_TAG_NAME.equals(node.getNodeName())) {
System.out.println( "\t\t" + node.getNodeName() + ":\t" + node.getAttributes().getNamedItem("name").getNodeValue());
}
printNodes(node.getChildNodes());
}
}
}
}
详细源码解析
DocumentBuilderFactory对象初始化
从上述的使用代码中可知,当需要使用DOM解析XML文件的时候,首先使用DocumentBuilderFactory对象调用newInstance初始化一个DocumentBuilderFactory对象,如下
public static DocumentBuilderFactory newInstance() {
// instantiate the class directly rather than using reflection
return new DocumentBuilderFactoryImpl();
}
public static DocumentBuilderFactory newInstance(
String factoryClassName, ClassLoader classLoader) {
if (factoryClassName == null) {
throw new FactoryConfigurationError("factoryClassName == null");
}
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
}
try {
Class<?> type = classLoader != null ?
classLoader.loadClass(factoryClassName)
: Class.forName(factoryClassName);
return (DocumentBuilderFactory) type.newInstance();
}
// ...... catch exception code delete
}
从DocumentBuilderFactory类中可以看到,newInstance函数有两个重载函数,一个是无参数的,另一个带了两个参数,当然我们这边使用的是无参函数,因此会直接返回一个DocumentBuilderFactoryImpl对象,而从JDK 1.6开始,添加了一个两个参数的重载函数,这边是留给我们自行扩展用的,在此暂不做赘述。
调用DocumentBuilderFactory的无参newInstance函数,直接返回一个初始化的DocumentBuilderFactoryImpl对象,而该对象在初始化时未重写其构造函数!
DocumentBuilderFactoryImpl对象的newDocumentBuilder函数调用
接下来在解析XML文件的过程中,调用了上述初始化的DocumentBuilderFactoryImpl对象的newDocumentBuilder函数
@Override
public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
// ...... throw exception code delete
DocumentBuilderImpl builder = new DocumentBuilderImpl();
builder.setCoalescing(isCoalescing());
builder.setIgnoreComments(isIgnoringComments());
builder.setIgnoreElementContentWhitespace(isIgnoringElementContentWhitespace());
builder.setNamespaceAware(isNamespaceAware());
return builder;
}
可以看到,在这个函数中,初始化了一个DocumentBuilderImpl对象,然后设置其几个重要的参数,在此简要列举其设置的参数和值
coalescing --> false
ignoreComments --> false
ignoreElementContentWhitespace --> false
namespaceAware --> false
当然这边还可以使用扩展后的自定义的DocumentBuilderFactoryImpl对象,用作扩展其他的数据值,暂不提
解析xml文件
在DocumentBuilderImpl对象初始化完成后,则将解析XML文件
public Document parse(String uri) throws SAXException, IOException {
// ...... throw exception code delete
InputSource in = new InputSource(uri);
return parse(in);
}
看到,在此处,直接初始化一个InputSource对象,然后将其作为参数,调用重载的parse函数
InputSource的初始化
public InputSource (String systemId) {
setSystemId(systemId);
}
紧接着,调用使用InputSource对象作为参数的重载parse函数
@Override
public Document parse(InputSource source) throws SAXException, IOException {
// ...... throw exception code delete
String namespaceURI = null;
String qualifiedName = null;
DocumentType doctype = null;
// 由于初始化时,均为设置这些值,因此此处这个返回均为null
String inputEncoding = source.getEncoding();
// 此处返回的为xml文件名
String systemId = source.getSystemId();
// 初始化一个DocumentImpl对象
DocumentImpl document = new DocumentImpl(
dom, namespaceURI, qualifiedName, doctype, inputEncoding);
document.setDocumentURI(systemId);
// KXmlParser对象的初始化,以及对应的操作
KXmlParser parser = new KXmlParser();
try {
parser.keepNamespaceAttributes();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, namespaceAware);
if (source.getByteStream() != null) {
parser.setInput(source.getByteStream(), inputEncoding);
} else if (source.getCharacterStream() != null) {
parser.setInput(source.getCharacterStream());
} else if (systemId != null) {
URL url = new URL(systemId);
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
// TODO: if null, extract the inputEncoding from the Content-Type header?
parser.setInput(urlConnection.getInputStream(), inputEncoding);
} else {
throw new SAXParseException("InputSource needs a stream, reader or URI", null);
}
if (parser.nextToken() == XmlPullParser.END_DOCUMENT) {
throw new SAXParseException("Unexpected end of document", null);
}
parse(parser, document, document, XmlPullParser.END_DOCUMENT);
parser.require(XmlPullParser.END_DOCUMENT, null, null);
}
// ...... catch exception code delete
return document;
}
这边我们分步骤来看
DocumentImpl的初始化
public DocumentImpl(DOMImplementationImpl impl, String namespaceURI, String qualifiedName, DocumentType doctype, String inputEncoding) {
super(null);
this.document = this;
this.domImplementation = impl;
this.inputEncoding = inputEncoding;
if (doctype != null) {
appendChild(doctype);
}
if (qualifiedName != null) {
appendChild(createElementNS(namespaceURI, qualifiedName));
}
}
从传入参数来看,后面四个参数均为null,而第一个参数为DOMImplementationImpl对象,而该对象是存在于DocumentBuilderImpl对象中,为全局直接初始化的DOMImplementationImpl对象
最后通过调用DocumentImpl对象的setDocumentURI函数,将xml文件名存入该对象中
初始化KXmlParser
KXmlParser parser = new KXmlParser();
try {
// 设置keepNamespaceAttributes的值为true
parser.keepNamespaceAttributes();
// parser.processNsp = false;
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, namespaceAware);
// null
if (source.getByteStream() != null) {
parser.setInput(source.getByteStream(), inputEncoding);
// null
} else if (source.getCharacterStream() != null) {
parser.setInput(source.getCharacterStream());
} else if (systemId != null) {
// URLConnection
URL url = new URL(systemId);
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
// TODO: if null, extract the inputEncoding from the Content-Type header?
// 通过解析xml文件,获取解析文件的编码方式,这边解析的为UTF-8
parser.setInput(urlConnection.getInputStream(), inputEncoding);
} else {
throw new SAXParseException("InputSource needs a stream, reader or URI", null);
}
// 还没有开始就结束了?说明根本就没有开始解析
if (parser.nextToken() == XmlPullParser.END_DOCUMENT) {
throw new SAXParseException("Unexpected end of document", null);
}
// 开始解析xml文件的内容
parse(parser, document, document, XmlPullParser.END_DOCUMENT);
parser.require(XmlPullParser.END_DOCUMENT, null, null);
}
// ...... catch exception code delete
如上,首先,先通过KXmlParser的setInput函数,获取到xml文件的编码方式,从这边也可以看到,这边是直接将这个xml文件完整导入到设备内存中的,同时在setInput函数中,会设置其对象中的一些默认数值(type设置为START_DOCUMENT)。
接下来,通过调用重载函数parse,对xml文件进行解析,因此,接下来分析一下这个重载函数
private void parse(KXmlParser parser, DocumentImpl document, Node node, int endToken) throws XmlPullParserException, IOException {
// 获取eventtype
int token = parser.getEventType();
while (token != endToken && token != XmlPullParser.END_DOCUMENT) {
// <?开头的行
if (token == XmlPullParser.PROCESSING_INSTRUCTION) {
String text = parser.getText();
int dot = text.indexOf(' ');
String target = (dot != -1 ? text.substring(0, dot) : text);
String data = (dot != -1 ? text.substring(dot + 1) : "");
node.appendChild(document.createProcessingInstruction(target, data));
// <!D开头的行
} else if (token == XmlPullParser.DOCDECL) {
String name = parser.getRootElementName();
String publicId = parser.getPublicId();
String systemId = parser.getSystemId();
document.appendChild(
new DocumentTypeImpl(document, name, publicId, systemId));
// 解析<!-- 开头的数据
} else if (token == XmlPullParser.COMMENT) {
if (!ignoreComments) {
node.appendChild(document.createComment(parser.getText()));
}
// 解析空行
} else if (token == XmlPullParser.IGNORABLE_WHITESPACE) {
if (!ignoreElementContentWhitespace && document != node) {
appendText(document, node, token, parser.getText());
}
// 解析文本,或者<![开头的数据
} else if (token == XmlPullParser.TEXT || token == XmlPullParser.CDSECT) {
appendText(document, node, token, parser.getText());
// 解析&开头的数据
} else if (token == XmlPullParser.ENTITY_REF) {
String entity = parser.getName();
if (entityResolver != null) {
// TODO Implement this...
}
String resolved = resolvePredefinedOrCharacterEntity(entity);
if (resolved != null) {
appendText(document, node, token, resolved);
} else {
node.appendChild(document.createEntityReference(entity));
}
// 解析< 开头的数据
} else if (token == XmlPullParser.START_TAG) {
if (namespaceAware) {
// Collect info for element node
String namespace = parser.getNamespace();
String name = parser.getName();
String prefix = parser.getPrefix();
if ("".equals(namespace)) {
namespace = null;
}
// Create element node and wire it correctly
Element element = document.createElementNS(namespace, name);
element.setPrefix(prefix);
node.appendChild(element);
for (int i = 0; i < parser.getAttributeCount(); i++) {
// Collect info for a single attribute node
String attrNamespace = parser.getAttributeNamespace(i);
String attrPrefix = parser.getAttributePrefix(i);
String attrName = parser.getAttributeName(i);
String attrValue = parser.getAttributeValue(i);
if ("".equals(attrNamespace)) {
attrNamespace = null;
}
// Create attribute node and wire it correctly
Attr attr = document.createAttributeNS(attrNamespace, attrName);
attr.setPrefix(attrPrefix);
attr.setValue(attrValue);
element.setAttributeNodeNS(attr);
}
// Recursive descent
token = parser.nextToken();
parse(parser, document, element, XmlPullParser.END_TAG);
// Expect the element's end tag here
parser.require(XmlPullParser.END_TAG, namespace, name);
} else {
// Collect info for element node
String name = parser.getName();
// Create element node and wire it correctly
// 创建一个Element对象
Element element = document.createElement(name);
// 将刚刚创建的element对象放到node,这个node中有一个ArrayList对象,
// 用作储存所有的child node
node.appendChild(element);
// 解析Attribute
for (int i = 0; i < parser.getAttributeCount(); i++) {
// Collect info for a single attribute node
String attrName = parser.getAttributeName(i);
String attrValue = parser.getAttributeValue(i);
// Create attribute node and wire it correctly
Attr attr = document.createAttribute(attrName);
attr.setValue(attrValue);
element.setAttributeNode(attr);
}
// Recursive descent
// 获取下一个token
token = parser.nextToken();
// 针对这个element中的所有子Node继续解析,迭代
parse(parser, document, element, XmlPullParser.END_TAG);
// Expect the element's end tag here
parser.require(XmlPullParser.END_TAG, "", name);
}
}
// 这一块模式END_TYPE和END_DOCUMENT以及START_DOCUMENT不做任何处理,
// 直接进入下一个token
token = parser.nextToken();
}
}
此后,将根据parser的nextToken来查找下一个标签的含义,并解析,最终解析出来的结果,均存放在一个个的Node中,得到最终的Node节点信息
DOM法解析XML文件总结
使用方法
- DocumentBuilderFactory通过newInstance无参函数,直接初始化一个DocumentBuilderFactory对象,然后通过newDocumentBuilder生成一个DocumentBuilder对象
- 通过DocumentBuilder对象,调用其内部的parse函数,进行XML文档解析
源码解析的大体流程
- DocumentBuilderFactoryImpl继承了DocumentBuilderFactory(抽象类),通过newInstance无参函数,直接初始化一个DocumentBuilderFactoryImpl对象,然后通过newDocumentBuilder生成一个DocumentBuilderImpl对象,该对象继承自DocumentBuilder抽象类
- 通过DocumentBuilderImpl对象,调用其内部的parse重载函数,进行XML文档解析,其中使用了迭代算法
- 在DocementBuidlerFactoryImpl对象中,JDK1.6中对newDocumentBuilder函数进行了重载,为客制化解析函数提供了可能
DOM法解析XML文件的优点和缺点
由上述的函数源码分析可知
- DocumentBuilderImpl在调用解析函数parse的时候,是将xml文件中的所有内容,转换成一个InputStream对象,然后对其进行解析,这样对于很大的文件来说,就会耗费非常大的内存,其只能解析相对不复杂的小xml文件
- 由于所有的数据操作和解析,均是在内存中进行,因此速度非常快
- 其生成的结果,可以看作一种树结构,非常简单和清晰