扩展 JDOM,定制自己的工具类

1. JDOM 简介

现在已经有很多的开源工具可以用来对 xml 文件进行解析了。像 SAX ,dom4j,Xerces 等。本文是用 JDOM 来对 XML 解析进行介绍。JDOM(参见 参考资料 ) 是用专门针对 JAVA 开发者开发的操作 XML 文档的工具。它的 API 非常的直观易用,降低了开发者门处理 XML 文件的门槛。在以后的章节中,读者可以充分体验到这一点。并且,JDOM 与现存的 XML 解析标准如 SAX(Simple API For XML) 以及 DOM(Document Object Model) 很好的兼容性。JDOM 并不是其它 XML 解析 API 的一个抽象层或者在性能上做一些改进,而是提供了一些健壮的,轻量级的方法来读写 XML 文件。事实上,JDOM 已经被收录到 JSR-102 内,成为了 JAVA 平台的一部分。



 
回页首

 

2. XML 结构以及 JDOM 提供的 XML 处理接口

2.1 XML 文件树形结构


图 1: XML文件树形结构 
2.1 XML文件树形结构

XML 文件是一个有严谨格式的文档,它的在结构上是一颗多叉树。比如一个简单的课程 XML 文件定义如下:


清单 1: 描述课程的 XML 文件

                

<?xml version="1.0" encoding="UTF-8"?>
<course cid="001">
<date>
        <from>2007.9</from>
        <to>2008.1</to>
</date>
<teacher tid="007">
        <teachername> 李三 </teachername>
</teacher>
<students>
        <student sid="1">
                <name> 张小明 </name>
                <age>12</age>
            …
            …
        </student>
        <student sid="2">
                <name> 陈小军 </name>
                <age>13</age>
        </student>
</students>
</course>

其中 course 就是树的根节点,其有三个孩子节点:date ,teacher ,students 分别描述了课程的时间,教课老师,以及上课学生。students 节点有两个孩子,他们都是 student 类型。每个 student 有一个属性 sid 代表学生学号,以及 name 孩子节点和 age 孩子节点代表学生姓名以及年龄,其中 name 的值张小明和 age 的值 12 就是树形结构中的非叶子节点。从该例子中我们可以看到,XML 文件的这种树形结构很好的描述了文档内容之间的所属关系和层次结构,因而使得我们可以方便的操作 XML 文档,例如添加节点,删除节点,循环遍历等。下小节将介绍 JDOM 中描述这种树形结构的一些基本的类,以及这些类所提供的一些方法。

2.2 JDOM 提供的一些基本的类及其方法介绍

JDOM 对 XML 文档提供了一些接口和类,使得我们可以方便从一个 XML 文件构建 DOM 对象,以及对构建好的 DOM 对象进行操作。下面简单介绍本文主要涉及到的类及其相应的方法。

1.XML 文档:Document 类

在 JDOM 中,从一个 XML 文档构建代表这个文档的 Document 对象,以及将一个 Document 对象保存为 XML 文档的 API 很简洁直观。如下清代所示:


清单 2: XML 文档与 Document 对象

                
// 初始化 xml document 的 initXmlSource 方法
private Document initXmlSource(String xmlSource) {
 SAXBuilder builder = new SAXBuilder();
        Document doc = null;
        try {
                doc = builder.build(new File(xmlSource));
        } catch (JDOMException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
        return doc;
}

// 保存 Document 对象到 XML 文件的 saveToXmlFile 方法
private void saveToXmlFile(Document doc,String xmlFilePath)
{
        XMLOutputter outputter=new XMLOutputter();
        try {
                outputter.output(doc, new FileOutputStream(xmlFilePath));
        } catch (FileNotFoundException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
}

SAXBuilder 类的 build 方法从一个 XML 文件中构建 Document 对象,而 XMLOutputter 类的 output 方法则可以将一个 Document 对象保存到 XML 文件中。上面的两个方法分别对这两个过程进行了封装。

2.节点类型: Element 类

Element 类代表了 XML 树形结构中的节点,但是它是有属性的节点。我们对 XML 文档进行操作,首先需要获得根节点 Root。Document 类中的 getRootElement() 方法返回一个 Element 类型的根节点。


清单 3

                
            …
Document doc = initXmlSource(xmlSource);
Element root=doc.getRootElement();
            …

Element 类提供了很多的方法,用来读写该节点的属性和值,获得其子节点,获得其父亲节点以及命名空间等。这些方法都可以直观的从命名上明白其作用。下面是本文中用到的一些方法介绍。

String getName() :
返回该节点的名字。
String getValue() :
返回该节点的值。
List getAttributes() :
返回此节点的属性列表。
String getAttributeValue(String name) :
返回此节点名字为  name 的属性值。
Element getChild(String name) :
返回该节点名字为  name 的孩子节点。
List getChildren() :
返回该节点所有的孩子节点。
Element getParentElement() :
返回该节点的父亲节点。
boolean removeChild(String name) :
删除该节点名字为  name 的子节点。
boolean removeAttribute(String name) :
删除该节点名字为  name 的属性。

当然,Element 类还提供了很多其他的方法,比如添加属性,添加一个叶子节点等,笔者在此就不详细列出了。

3.节点属性: Attribute 类

Attribute 类描述了节点的属性。我们可以通过该类的 getName() 方法和 getValue 方法来获得该属性的名字和值。

2.3 处理 XML 文件时遇到的问题

由上可知,JDOM 提供的这些 API 能使我们方便的对 XML 中节点进行操作。以节点为中心,我们可以很容易得到节点的名字、值、属性、父亲节点以及叶子节点等相关的信息。但是,在一些特定的应用场合,这些 API 不能直接提供给我们所需要的功能。


图 2: 需要从清单 1 中获得的信息 
需要从清单 1 中获得的信息

比 如对上图中清单 1 所描述的课程,我们需要获得该课程的 cid,或者只需要该课程的老师名字 tearchername,或者需要获得该课程中所有学生的名字 name。在这样的情况下,我们只知道该 XML 文档中有一个 cid 的属性,或者有许多名字为 name 的节点,但是并不知道这些属性或者节点在 XML 树形结构的第几层上。因此,需要对整个文档进行搜索,直到找到需要的信息。这样的搜索可能在第一次找到匹配的元素时停止,像课程的 cid,因为它是唯一的;也有可能需要搜索全部的 XML 文档,像学生的名字 name,因为学生不只一个。像其它的 XML 解析器一样,JDOM 没有直接为我们提供满足这类需要的 API。



 
回页首

 

3. 扩展 JDOM,定制自己的工具类

由上可知,通过 JDOM 的 API,我们可以方便的从一个节点得到其父亲节点或者叶子

节点。在这些 API 的基础上,我们可以构建出针对于 XML 文档这种树形结构的遍历搜索算法。以这种算法为核心,定制工具类,从而提供满足特定应用需要的方法。比如类似 AJAX DOM 解释中的 getElementsByTagName(String name) 这样的方法。

3.1 JDOM 下载以及开发环境的准备

JDOM 的二进制版本可以在 JDOM 站点免费下载(参见 参考资料 )。你也可以下载源代码,自己编译。目前 JDOM 的最新版本是 JDOM1.0。笔者所使用的开发环境是 Eclipse Europa,此开发工具可以在 Eclipse 网站(参见 参考资料 )免费下载;JDK 是 1.6,你也可以在 Sun 的站点下载,并且在操作系统中配置好环境变量。在 Eclipse 中创建一个 JAVA 工程,在工程的 Build Path 中将 JODM 加入,这样就可以使用 JDOM 进行开发了。


图 3: 开发环境 
开发环境

3.2 基于 JDOM 构建 XML 文件处理工具类

笔者针对于应用构建的基于 JDOM 的工具主要由三个类组成。如下是三个类的 UML 关系图。JdomTool.java 定义了一组接口方法,提供了我们解决应用所需的一组抽象 API,隐藏具体方法的实现细节。当然这个接口仅提供了解决笔者应用中所遇到的问题的一些方法,并没有企图解决所有的问题,这些方法及其他们能提供的功能将在工具类接口小节中详细介绍。JdomToolImpl 依赖 JdomToolHelper 实现了 JdomTool 接口中定义的方法。JdomToolHelper 封装了相应搜索算法的实现,使得 JdomTool 可以将算法这一部分的工作委托到 JdomToolHelper 中。这样的设计只是为了向外提供工具统一的访问接口,并保证每个类对自己的职责负责。


图 4: 工具类 UML 类图 
工具类 UML 类图

下面详细介绍这三个类的代码实现。

3.2.1 工具类接口


清单 4: 接口 JdomTool.java

                
package com.iems.jhw.util;
import java.util.List;
public interface JdomTool {
public String getNodeAttributeValue(String NodeName, String attribute,
                String xmlSource);
public String getValueByNodeName(String name, String xmlSource);
public List<String> getValuseByNodeName(String name, String xmlSource);
public void removeNodeByName(String name, String xmlSource);
public String getXmlValueByTagName(String name, String xmlSource);
public List<String> getXmlvaluesByTagName(String name, String xmlSource);
}

JdomTool 接口定义了工具所要提供的方法:

  • 1.public String getNodeAttributeValue(String nodeName, String attribute,String xmlSource)

    该方法定义了根据 nodeName 找到节点,并且将其属性名为 attribute 的属性值返回,xmlSource 表示 XML 原文件地址。

  • 2.public String getValueByNodeName(String name, String xmlSource)

    该方法的功能是,在 XML 文件中找到第一个名字为 name 的节点,并返回其值。

  • 3.public List<String> getValuseByNodeName(String name, String xmlSource)

    该方法的功能是,返回一个 List ,该 List 包含了 XML 文件中所有名为 name 的节点的值。

  • 4.public void removeNodeByName(String name, String xmlSource)

    该方法的功能是,删除 XML 文件中所有名字为 name 的节点。

  • 5.public String getXmlValueByTagName(String name, String xmlSource)

    该方法的功能是,在 XML 文件中返回第一个节点名或者属性名为 name 的值。该方法在从 XML 文件中取某一特殊的(多数情况下该名字在文件中是唯一的)节点或者属性的值时用到。

  • 6.public List<String> getXmlvaluesByTagName(String name, String xmlSource)

    该方法的功能是,返回一个 List ,其包含了 XML 文件中所有名字为 name 的节点

    或者节点属性的值。

3.2.2 算法实现类

算法实现类 JdomToolHelper.java 对接口中那些方法的具体实现算法进行了定义。下面是该类的算法实现的代码部分。


清单 5: private Element getElementByName(String elementName, Element root)

                
            …
private Element element = null;
private Element getElementByName(String elementName, Element root) {
        // 如果是根元素,直接返回
        if (root.getName().equals(elementName)) {
                element = root;
                elementFind = true;
                System.out.println("element finded");
                return element;
        } else {
                List<Element> childElements = root.getChildren();
                Iterator<Element> it = childElements.iterator();
                while (it.hasNext()) {
                        // 如果找到元素,则返回
                        if (elementFind)
                                return element;
                        else {
                                Element childElement = (Element) it.next();
                                System.out.println(childElement.getName());
                                element = getElementByName(elementName, childElement);
                        }
                }
                return element;
        }
}
            …

该算法实现了如何在 XML 文件中使用深度优先的算法来循环扫描 XML 文档,直到找到所需要的节点为止。


清单 6: public String findAttributeValueByElementName(String elementName, String attributeName, Element root)

                
    …        
public String findAttributeValueByElementName(String elementName,
                String attributeName, Element root) {
        Element e = getElementByName(elementName, root);
        // System.out.println(e.getName());
        return e.getAttribute(attributeName).getValue();
}
            …

该算法实现了从 XML 文件中找到名字为 elementName 的节点,返回此节点名为 attributeName 属性的值。


清单 7: public List<Element> getElementsByName(String elementName, Element root)

                
            …
List<Element> elementList = null;
public List<Element> getElementsByName(String elementName, Element root) {
        if (elementList == null)
                elementList = new ArrayList<Element>();
        System.out.println(root.getName());
        if (root.getName().equals(elementName))
                elementList.add(root);
        else {
                List<Element> childElements = root.getChildren();
                Iterator<Element> it = childElements.iterator();
                while (it.hasNext()) {
                        Element childElement = it.next();
                        getElementsByName(elementName, childElement);
                }
        }
        // System.out.println(elementList.size());
        return elementList;
}
            …

该算法实现了在 XML 文件中找到所有名为 elementName 的节点,返回一个含有这些节点的 List。


清单 8: public void removeElementByName(String elementName, Element root)

                
            …
public void removeElementByName(String elementName, Element root) {
        Element targetElement = getElementByName(elementName, root);
        targetElement.getParentElement()
                        .removeChildren(targetElement.getName());
}
            …

该算法实现了从 XML 中删除名为 elementName 的节点。


清单 9: public String findValueByName(String name, Element root)

                
            …
private String value = null;
private boolean valueFind = false;
public String findValueByName(String name, Element root) {
        if (valueFind)
                return value.trim();
        // 先深度搜索属性
        List attributes = root.getAttributes();
        Iterator attrIt = attributes.iterator();
        while (attrIt.hasNext()) {
                Attribute attr = (Attribute) attrIt.next();
                // System.out.println(attr.getName());
                // 如果属性中找到所需要的类型值
                if (attr.getName().equals(name)) {
                        value = attr.getValue();
                        // System.out.println("find the value : "+value);
                        valueFind = true;
                        return value.trim();
                }
        }
        // 如果没有找到,则在其孩子中递归
        List children = root.getChildren();
        Iterator childrenIt = children.iterator();
        while (childrenIt.hasNext()) {
                if (valueFind)
                        return value.trim();
                Element child = (Element) childrenIt.next();
                if (child.getName().equals(name)) {
                        // 若不在值元素中,则到属性中找
                        if (child.getValue().equals("") || child.getValue() == null)
                                value = findValueByName(name, child);
                        else {
                                value = child.getValue();
                                // System.out.println("find the value : "+value);
                                valueFind = true;
                                return value.trim();
                        }
                } else
                        value = findValueByName(name, child);
        }
        return value;
}
            …

该算法实现了从 XML 文件的根节点开始找,按照深度优先递归遍历 XML 文件直到找到节点名字或者属性名字为 name 的位置,返回其值。


清单 10: public List<String> findValuesByName(String name, Element root)

                
            …
private List<String> values = new ArrayList<String>();
public List<String> findValuesByName(String name, Element root) {
        // 先从属性中找值
        List attributes = root.getAttributes();
        Iterator attrIt = attributes.iterator();
        while (attrIt.hasNext()) {
                Attribute attr = (Attribute) attrIt.next();
                // System.out.println(attr.getName());
                // 如果属性中找到所需要的类型值
                if (attr.getName().equals(name)) {
                        value = attr.getValue();
                        // System.out.println("find the value : "+value);
                        values.add(value);
                }
        }
        // 然后在孩子中递归找值
        List children = root.getChildren();
        Iterator childrenIt = children.iterator();
        while (childrenIt.hasNext()) {
                Element child = (Element) childrenIt.next();
                // 如果孩子的名字为 name
                if (child.getName().equals(name)) {
                        // 若不在孩子中,则到属性中找
                        if (child.getValue().equals("") || child.getValue() == null)
                                findValuesByName(name, child);
                        else {
                                value = child.getValue();
                                // System.out.println("find the value : "+value);
                                values.add(value);
                        }
                        // 递归
                } else
                        findValuesByName(name, child);
        }
        return values;
}
}

该算法实现了从 XML 文件的根节点开始找,按照深度优先递归遍历 XML 文件将名字

为 name 的节点或者属性的值放入到 List 中,返回 List 。

3.2.3 工具类接口的实现类


清单 11 JdomToolImpl.java

                
package com.iems.jhw.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
/**
 * 接口 JdomTool 的实现类
 */
public class JdomToolImpl implements JdomTool {
// 初始化 xml document 的 initXmlSource 方法
private Document initXmlSource(String xmlSource) {
        SAXBuilder builder = new SAXBuilder();
        Document doc = null;
        try {
                doc = builder.build(new File(xmlSource));
        } catch (JDOMException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
        return doc;
}
// 保存 Document 对象到 XML 文件的 saveToXmlFile 方法
private void saveToXmlFile(Document doc,String xmlFilePath)
{
        XMLOutputter outputter=new XMLOutputter();
        try {
                outputter.output(doc, new FileOutputStream(xmlFilePath));
        } catch (FileNotFoundException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
}
// 在 XML 文件中返回第一个节点名或者属性名为 name 的值
public String getXmlValueByTagName(String name, String xmlSource) {
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        return helper.findValueByName(name, doc.getRootElement());
}
// 返回一个 List,其包含了 XML 文件中所有名字为 name 的节点或者节点属性的值
public List<String> getXmlvaluesByTagName(String name, String xmlSource) {
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        return helper.findValuesByName(name, doc.getRootElement());
}
// 根据 nodeName 找到节点,并且将其属性名为 attribute 的属性值返回
public String getNodeAttributeValue(String NodeName, String attribute,
                String xmlSource) {
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        return helper.findAttributeValueByElementName(NodeName, attribute, doc
                        .getRootElement());
}
// 在 XML 文件中找到第一个名字为 name 的节点,并返回其值
public String getValueByNodeName(String name, String xmlSource) {
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        return helper.getElementByName(name, doc.getRootElement()).getValue();
}
// 返回一个 List,该 List 包含了 XML 文件中所有名为 name 的节点的值
public List<String> getValuseByNodeName(String name, String xmlSource) {
        List<String> values=new ArrayList<String>();
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        List<Element> nodes = helper.getElementsByName(name, doc
                        .getRootElement());
        Iterator<Element> it = nodes.iterator();
        while (it.hasNext()) {
                Element e=it.next();
                values.add(e.getValue());
        }
        return values;
}
// 删除 XML 文件中所有名字为 name 的节点
public void removeNodeByName(String name,String xmlSource){
        JdomToolHelper helper = new JdomToolHelper();
        Document doc = initXmlSource(xmlSource);
        helper.removeElementByName(name, doc.getRootElement());
        saveToXmlFile(doc, xmlSource);
}
}

JdomToolImpl.java 是 JdomTool 接口的实现类,它将算法的具体实现委托给 JdomToolHelper 。其中私有方法 initXmlSource(String xmlSource) 是初始化 XML 文件,得到 XML 文件的 Docmument 对象,而 saveToXmlFile(Document doc,String xmlFilePath) 方法是将代表 XML 文档的 Document 对象保存到由 xmFilePath 所指向的磁盘文件中。

3.2.4 工具类的使用实例代码

下面的代码演示了如何使用定制的工具类中的一些方法来对清单 1 所给的课程 XML 文件进行操作。


清单 12: JdomTool 使用的实例代码。

                
package com.iems.jhw.util;
import java.util.Iterator;
import java.util.List;
public class JdomToolTester {
public static void main(String[] args) {
        JdomTool jdomTool = new JdomToolImpl();
// course.xml 文件在 jdomTool 类相同的路径下
        String xmlSource =jdomTool.getClass().getResource("course.xml").getPath();
//                找到课程 from 元素的值
        String value = jdomTool.getXmlValueByTagName("from", xmlSource);
        System.out.println("from: "+value);
//                找到课程所有学生的名字
        List<String> values = jdomTool.getValuseByNodeName("name", xmlSource);
        Iterator<String> it = values.iterator();
        while (it.hasNext()) {
                System.out.println("name: "+it.next());
        }
//                找课程老师 teacher 的编号 tid 属性        
        System.out.println("tid: "+jdomTool.getNodeAttributeValue("teacher", "tid",
                        xmlSource));
        System.out.println("test finished...");
}
}

在 Eclipse 中运行上面的代码,笔者得到了如下的结果:


图 5: 运行代码的结果 
运行代码的结果

定制的工具类提供了一组更为方便的操作,解决了笔者应用中遇到的特定问题。当然读者可以根据这样的扩展思路,来定制满足自己需要的工具类,甚至可以通过更为优美的设计使得这些 API 具有良好的灵活性,来应对普遍的应用问题,达到代码更高程度的复用。

总结

这 篇文章主要是展示了如何使用开源的 JDOM 框架来扩展定制自己的 XML 处理工具,使其能够更好的解决自己所遇到的问题。JDOM 为 XML 文件的读写提供了容易使用、高效的 API,但是其不可能解决所有的问题。因此,对于其它一些 XML 解析的共性问题,我们可以扩展已有的 API 构造自己的 API。笔者提供的工具类,也是如此。希望这篇文章能够引导读者对 XML 文件处理以及 JODM 的使用有一个更好的认识。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值