DOM操作XML文件

使用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类还提供了判断当前解析器是否存在命名空间解析、验证等配置,以及提供了设置EntityResolverErrorHandler的接口。这里使用EntityResolverErrorHandler只是重用SAXAPI,并不表示DOM解析的内部实现一定要基于SAX,然而貌似JDK自带的DOM解析内部使用的引擎就是SAXT_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实例,需要使用DocumentBuilderFactoryDocumentBuilderFactory提供了多种查找DocumentBuilder实现类的方法;DocumentBuilderFactory本身也是抽象类,它提供了两个静态方法来创建DocumentBuilderFactory实例:

public static DocumentBuilderFactory newInstance();

public static DocumentBuilderFactory newInstance(String factoryClassName, ClassLoader classLoader);

不带参数的newInstance()方法使用以下步骤查找DocumentBuilderFactory的实现类:

1.       查看系统属性中是否存在javax.xml.parsers.DocumentBuilderFactorykey的定义,如果存在,则使用该key定义的值作为DocumentBuilderFactory的实现类。

2.       查找${java.home}/lib/jaxp.properties属性文件中是否存在javax.xml.parsers.DocumentBuilderFactorykey的定义,若存在,则使用该属性文件中以该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接口来做这件事情。这属于XLSTEXtensible Stylesheet Language)的范畴,不过这里不打算对其做详细介绍,主要关注如何将Document实例输出成XML文件。

Transformer提供了transform方法将Document实例写入指定的流中:

public abstract void transform(Source xmlSource, Result outputTarget);

其中Source接口定义了输入源,它可以是DOMSource,也可以是SAXSource,或者是自定义的其他Source子类,这里主要介绍DOMSourceSource接口定义了systemId属性,它表示XML源的位置,XML源不是从URL中获取的源来说,它为null。具体定义如下:

public interface Source {

    public void setSystemId(String systemId);

    public String getSystemId();

}

DOMSource是对Source的一个具体实现,它接收NodesystemId信息:

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的实现,如DOMResultStreamResult等。这里只介绍StreamResult,表示其输出目的是流,我们可以提供WriterOutputStream等实例来接收这些输出:

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();

类似DocumentBuilderTransformer通过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提供了创建TransformerTemplates的方法,同时也提供了在创建这两个实例时可以设置的一些配置方法:

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());

    }

}


上一篇:OSS图片处理服务上线


下一篇:详解 WebRTC 传输安全机制:一文读懂 DTLS 协议