XML解析技术1-使用JAXP开发包对XML进行dom解析

XML解析开发包 :Jaxp(sun公司),Jdom,dom4j,XStream

用JAXP虽然是最不常用的一种解析方式,但是因为他是sun公司的标准,所以还是要简单学习一下。

一、JAXP解析XML—数据读取

jaxp包是j2se的一部分,在javax.xml.parses包里,对几个工厂类进行调用就可以xml文档的dom或者sax 的解析器。

javax.xml.parsers包在jdk1.4之后已经是自带的了。

在API文档中可以直接查到这个包里的DocumentBuilderFactory类,用来创建DOM模式的解析器对象。

在这里插入图片描述

DocumentBuilderFactory类是一个抽象工厂类,它不能直接实例化,但是提供了newInstance方法,可以根据本地平台默认安装的解析器,自动创建一个工厂对象并返回

在这里插入图片描述

在newInstance之后获得一个实例,就可以调用里面的newDocumentBuilder方法,返回一个dom解析器DocumentBuilder类的实例,就可以用这个解析器里面的各种parse方法对XML文档进行解析了。

这些parse方法返回的都是document对象,也就是拿到了XML文档进行dom解析之后的结果,document对象里是所有的元素、属性等等节点的组成,利用这个document对象,可以访问到里面的任意一个节点。

我们使用刚才的book.xml文档作为测试来源:

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
    <book category="CHILDREN">
        <title>Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
    <book category="WEB">
        <title>Learning XML</title>
        <author>Erik T. Ray</author>
        <year>2003</year>
        <price>39.95</price>
    </book>
</bookstore>

然后写一个测试类来对他进行dom解析。

按照上面这个类的使用方法,首先创建工厂,然后得到dom解析器,然后解析xml文档得到document,然后对里面的节点进行操作。

 @Test
 public void Parse() throws Exception {
       //拿到一个工厂对象
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       //得到解析器
       DocumentBuilder builder=factory.newDocumentBuilder();
       //对目标xml文档进行解析
       Document document=builder.parse("src/xml/book.xml");
 }

运行成功。

下一步要对解析之后的document进行操作,我们查看document的api文档,里面肯定有很多get方法。

在这里插入图片描述

我们使用getElementsByTagName,这个方法是返回的elements,多个元素,它的返回值可以看到是一个NodeList类型,点进去可以看到NodeList提供的方法。

我们以titile元素为例,获得所有内容输出:

 @Test
 public void handleElem() throws Exception {
       //拿到一个工厂对象
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       //得到解析器
       DocumentBuilder builder=factory.newDocumentBuilder();
       //对目标xml文档进行解析
       Document document=builder.parse("src/xml/book.xml");
       //根据名字获取document里的某一元素的所有内容
       NodeList list=document.getElementsByTagName("title");
       Node node=list.item(0);
       System.out.println(node.getTextContent());
 }

运行结果:

在这里插入图片描述

可以看到输出了我们xml文档里的第一个book元素的content。

这里为什么用一个Node对象保存获取到的内容,因为在dom解析下,xml文档的每一个组成部分都会用一个对象表示,标签的Element、属性Attr等。但是都是继承自Node类的,所以我们获取到的任意节点都可以先用Node对象来保存。

在根据名称获取某一个元素之前,我们往往需要先打印出所有的元素名,这样才知道XML文档里i到底有多少元素,我们来写一个方法来输出所有的元素。由于存储的方式是一个一个对象组成的树状结构,显然在遍历的时候需要用到递归,先获取到根节点,然后用递归方法去输出每一个孩子节点的名称。

 @Test
 public void readElem1() throws Exception {
       //拿到一个工厂对象
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       //得到解析器
       DocumentBuilder builder=factory.newDocumentBuilder();
       //对目标xml文档进行解析
       Document document=builder.parse("src/xml/book.xml");
       
       //获取所有的元素,从根节点开始,然后到子节点,递归遍历每一个输出节点名
       Node  root=document.getElementsByTagName("bookstore").item(0);
       printChild(root);
 }
 //递归查看孩子节点的方法
 public void printChild(Node node) {
       System.out.println(node.getNodeName());//输出这个节点名
       NodeList list=node.getChildNodes();//getChildNodes保存所有这个节点的子节点
       for(int i=0;i<list.getLength();++i) {
            Node child=list.item(i);
            printChild(child);//递归调用本方法
       }
 }

这里面在查看子节点的时候:

  • 1.首先获取到根节点;
  • 2.调用printChild方法,根节点的名字输出,然后用一个节点list保存所有的子节点。我们的xml文档里根节点是bookstore,子节点有两个book,因此这个list里循环就会有两次;
  • 3.for循环里对于每一个子节点,用一个node保存子节点,然后调用这个子节点的printChild方法,递归。我们的xml文件里,book节点保存在一个node里,调用book的printChild方法,首先输出他的名字,然后继续调用他的后面。

输出结果是这样的:

在这里插入图片描述

能够看到,bookstore元素下来的是一段文档#text,这是一个对象,在xml文档中我们看到这是回车和缩进,是属于bookstore的内容;然后打印book孩子,然后下来是book的内容,是#text,在xml文档中是回车和缩进(book的属性category="CHILDREN"并没有输出,因为他属于节点内部);接下来是title名,然后两段#text,分别是Harry Potter和回车缩进……

这里也可以理解到,在XML语法详解的时候所说,XML的空格和换行也会被当做标签(元素)的内容进行处理,有空格换行的写法,和直接连在一起的写法是不同的。

刚才也说到了,这里面只输出了节点(元素)名称,其中有些包含的属性内容没有输出,那么属性怎么输出呢?

 @Test
 public void readAttr() throws Exception {
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       DocumentBuilder builder=factory.newDocumentBuilder();
       Document document=builder.parse("src/xml/book.xml");
       
       //前提我们知道这个是一个Element,所以想要用子类更多的方法,就强转为Element而不是继续使用Node
       Element element=(Element)  document.getElementsByTagName("book").item(0);
       String attribute=element.getAttribute("category");
       System.out.println(attribute);
       
       Element element2=(Element)  document.getElementsByTagName("book").item(1);
       String attribute2=element2.getAttribute("category");
       System.out.println(attribute2);
 }

针对XML里的和,还是先通过解析器解析文档,存储解析结果在document里,然后通过元素名称获取到元素节点,这里我们没有像前面测试解析的时候用Node对象保存解析出的每一个元素,因为Node作为父类节点,方法并不全,获取到每个具体元素的属性内容方法是没有的,因此我们采用更具体的Element类来存储节点,这里的前提是我们知道getElementsByTagName方法获得到的这个元素确实是一个元素类型,否则强转类型一定会失败。

这个测试里我们把两个book元素的属性都进行了输出,测试结果是这样的:

在这里插入图片描述

读取数据到这里就已经结束了,因为XML文档表示数据的方式也就只有两种,一个是标签和标签的内容,再就是标签里的属性。

二、JAXP解析XML—数据上添加标签(元素)

首先,不变的是我们要先创建工厂、创建解析器、解析指定的xml文档为一个document对象。

然后我们添加节点,new一个element对象,设置对象名(也就是标签名),然后将里面的内容设置为
指定的内容,接着在这个对象树里增加这个节点,增加的位置进行指定,这里我们在第一本书的第一个book标签后面增加这个新增的节点。

 @Test
 public void addBookAuthor() throws Exception {
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       DocumentBuilder builder=factory.newDocumentBuilder();
       Document document=builder.parse("src/xml/book.xml");
       
       //创建要加入的标签(元素)节点
       Element author=document.createElement("author");
       author.setTextContent("John Archie");
       
       //选择给第一本书里增加
       Element book=(Element)  document.getElementsByTagName("book").item(0);
       book.appendChild(author);
}

但是到这里为止,我们只是把内存里的document进行了更改,然而并没有把更改写入本来的xml文件,因为我们在操作的时候,是先解析XML文档,解析出document对象,这里创建对象都是在内存中新进行的操作。

所以要想彻底进行更改,还要把更改后的内容重新写回XML文档。

javax.xml.transform包里的Transformer类用于把代表XML文件的Document对象转换为某种格式之后进行输出,例如把xml文件应用样式表之后转换成一个html文档,利用这个document对象自然也就可以重新写入到一个xml文件中。

Transformer类通过transform方法完成转换操作,Transformer对象要通过TransformerFactory的newInstance方法获得。
【public abstract void transform(Source xmlSource, Result outputTarget)】
该方法接收一个源和一个目的地。
source接口的实现类有一个DOMSource,我们使用的是dom解析,所以我们用DOMSource类来关联我们要转换的document对象,在这里我们就利用DOMSource创建source,构造器参数是解析过,并且已经进行更改的的document;result也是一个接口,实现类我们选择用StreamResult对象来表示数据的目的地,而不是DOMResult,因为我们要的写入直接是xml文件,填入的就是本来的xml文档。

这样完整的代码就是这样:

 @Test
 public void addBookAuthor() throws Exception {
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       DocumentBuilder builder=factory.newDocumentBuilder();
       Document document=builder.parse("src/xml/book.xml");
       
       //创建要加入的标签(元素)节点
       Element author=document.createElement("author");
       author.setTextContent("John Archie");
       
       //选择给第一本书里增加
       Element book=(Element)  document.getElementsByTagName("book").item(0);
       book.appendChild(author);
       
       //把更新后的内存写回XML文档
       TransformerFactory  transformerFactory=TransformerFactory.newInstance();//拿到一个工厂
       Transformer  transformer=transformerFactory.newTransformer();//拿到一个转换器
       DOMSource source=new DOMSource(book);//创建输入源就是transform的source
       StreamResult result=new StreamResult(new  FileOutputStream("src/xml/book.xml"));//创建输出位置一个新输出流transform的result
       transformer.transform(source, result);
       
 }

再次运行程序,点回XML文档之后就会看到已经提示文档有改动了

在这里插入图片描述

在这里插入图片描述

多出了author元素并且内容是“John Archie”。

但是这个方法把节点添加到了第一个book元素的最后,显然,我们希望的位置往往是指定位置,这样逻辑更加合理,这个例子里,希望他放在第一个author元素的后面。因此我们可以稍作修改。

在增加的操作部分,我们使用的是Element类的append方法,将节点加入的。查看api发现element类并没有对应指定位置的方法,因此查看父类Node类,发现有一个方法:
【insertBefore(Node newChild, Node refChild)】
在现有的子节点refChild之前插入节点newChild。

因此我们重新修改代码:

 @Test
 public void addBookAuthor2() throws Exception {
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       DocumentBuilder builder=factory.newDocumentBuilder();
       Document document=builder.parse("src/xml/book.xml");
       
       //创建要加入的标签(元素)节点
       Element author=document.createElement("author");
       author.setTextContent("John Archie");
       
       //选择给第一本书元素里增加,参考节点为第一个author
       Element refChild=(Element)  document.getElementsByTagName("author").item(0);
       Element book=(Element)  document.getElementsByTagName("book").item(0);
       book.insertBefore(author, refChild);
       
       //把更新后的内存写回XML文档
       TransformerFactory  transformerFactory=TransformerFactory.newInstance();//拿到一个工厂
       Transformer  transformer=transformerFactory.newTransformer();//拿到一个转换器
       DOMSource source=new DOMSource(document);//利用DOMSource创建source,参数是解析过,并且已经进行更改的的document
       StreamResult result=new StreamResult(new  FileOutputStream("src/xml/book.xml"));//创建输出位置一个新输出流transform的result
       transformer.transform(source, result);
       
 }

可以看到,又多出了一个author并且在本来的第一个author元素之前。(这里已经可以发现,如果相同元素名的元素非常多,那么很难确定item的index,所以在标签(元素)里加入id属性是很重要的,因为在获取更改的时候,有byId的方法)

三、JAXP解析XML—某个元素上增加属性

在前一个增加元素的方法里,我们已经实现了如何获取到某个元素,我们只要寻找找到固定元素之后,能够改变属性的方法即可,显然,element类里的

【setAttribute(String name, String value)】方法,就是实现这个功能的。

核心代码就是:

       Element book=(Element)  document.getElementsByTagName("book").item(1);
       book.setAttribute("category", "XMLPARSE");

前面的获取,和后面更改之后的再次写入修改,是一样的。

运行之后,可以看到结果

在这里插入图片描述

四、JAXP解析XML——删除某个标签(元素)

删除节点的思路很简单,因为实际的内存中,存储方式是对象树,因此删除某个节点相当于让整个节点的父节点指向这个节点的子节点。查找api文档,发现element父类Node直接就有删除某个元素的子节点的方法:

    【Node removeChild(Node oldChild)】

因此我们还是一样,前面开始解析,删除操作完成之后,后面把改变重新写入xml文档。

 @Test
 public void removeElem() throws Exception {
       DocumentBuilderFactory  factory=DocumentBuilderFactory.newInstance();
       DocumentBuilder builder=factory.newDocumentBuilder();
       Document document=builder.parse("src/xml/book.xml");
       
       //选择第二本书的author节点
       Element element=(Element)  document.getElementsByTagName("author").item(1);
       //得到他的父节点
       Element father=(Element)  document.getElementsByTagName("book").item(1);
       //用父节点的删除方法删除这个子节点
       father.removeChild(element);
       
       //把更新后的内存写回XML文档
       TransformerFactory  transformerFactory=TransformerFactory.newInstance();//拿到一个工厂
       Transformer  transformer=transformerFactory.newTransformer();//拿到一个转换器
       DOMSource source=new DOMSource(document);//利用DOMSource创建source,参数是解析过,并且已经进行更改的的document
       StreamResult result=new StreamResult(new  FileOutputStream("src/xml/book.xml"));//创建输出位置一个新输出流transform的result
       transformer.transform(source, result);
 }

运行结果:

在这里插入图片描述

我们用了两个节点来进行操作,分别保存他自己和父节点,父节点的获取方式仍然是用元素名,但是更方便的做法是,要删除的元素本就有getParentNode方法可以调用,然后get之后直接再removeChild即可。

关键部分的代码可以更改如下:

       Element element=(Element)  document.getElementsByTagName("author").item(1);
       element.getParentNode().removeChild(element);

其他设置,更改的方式相差不大,仍然是这样的基本步骤,核心方法,都在api里提供了。

原文链接:加载失败,请重新获取