Android基础之DOM解析XML文件

1,245 阅读6分钟

「这是我参与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, nullnull); 
    }
    // ...... 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, nullnull); 
} 
// ...... 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文件总结

 使用方法

  1. DocumentBuilderFactory通过newInstance无参函数,直接初始化一个DocumentBuilderFactory对象,然后通过newDocumentBuilder生成一个DocumentBuilder对象
  2. 通过DocumentBuilder对象,调用其内部的parse函数,进行XML文档解析

源码解析的大体流程

  1. DocumentBuilderFactoryImpl继承了DocumentBuilderFactory(抽象类),通过newInstance无参函数,直接初始化一个DocumentBuilderFactoryImpl对象,然后通过newDocumentBuilder生成一个DocumentBuilderImpl对象,该对象继承自DocumentBuilder抽象类
  2. 通过DocumentBuilderImpl对象,调用其内部的parse重载函数,进行XML文档解析,其中使用了迭代算法
  3. 在DocementBuidlerFactoryImpl对象中,JDK1.6中对newDocumentBuilder函数进行了重载,为客制化解析函数提供了可能

 DOM法解析XML文件的优点和缺点

由上述的函数源码分析可知

  1. DocumentBuilderImpl在调用解析函数parse的时候,是将xml文件中的所有内容,转换成一个InputStream对象,然后对其进行解析,这样对于很大的文件来说,就会耗费非常大的内存,其只能解析相对不复杂的小xml文件
  2. 由于所有的数据操作和解析,均是在内存中进行,因此速度非常快
  3. 其生成的结果,可以看作一种树结构,非常简单和清晰