现在已经有很多的开源工具可以用来对 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 平台的一部分。
|
|
XML 文件是一个有严谨格式的文档,它的在结构上是一颗多叉树。比如一个简单的课程 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 中描述这种树形结构的一些基本的类,以及这些类所提供的一些方法。
JDOM 对 XML 文档提供了一些接口和类,使得我们可以方便从一个 XML 文件构建 DOM 对象,以及对构建好的 DOM 对象进行操作。下面简单介绍本文主要涉及到的类及其相应的方法。
1.XML 文档:Document
类
在 JDOM 中,从一个 XML 文档构建代表这个文档的 Document
对象,以及将一个 Document
对象保存为 XML 文档的 API 很简洁直观。如下清代所示:
// 初始化 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
类型的根节点。
… Document doc = initXmlSource(xmlSource); Element root=doc.getRootElement(); … |
Element
类提供了很多的方法,用来读写该节点的属性和值,获得其子节点,获得其父亲节点以及命名空间等。这些方法都可以直观的从命名上明白其作用。下面是本文中用到的一些方法介绍。
- 返回该节点的名字。
- 返回该节点的值。
- 返回此节点的属性列表。
-
返回此节点名字为
name
的属性值。 -
返回该节点名字为
name
的孩子节点。 - 返回该节点所有的孩子节点。
- 返回该节点的父亲节点。
-
删除该节点名字为
name
的子节点。 -
删除该节点名字为
name
的属性。
String getName()
:
String getValue()
:
List getAttributes()
:
String getAttributeValue(String name)
:
Element getChild(String name)
:
List getChildren()
:
Element getParentElement()
:
boolean removeChild(String name)
:
boolean removeAttribute(String name)
:
当然,Element
类还提供了很多其他的方法,比如添加属性,添加一个叶子节点等,笔者在此就不详细列出了。
3.节点属性: Attribute
类
Attribute
类描述了节点的属性。我们可以通过该类的 getName()
方法和 getValue
方法来获得该属性的名字和值。
由上可知,JDOM 提供的这些 API 能使我们方便的对 XML 中节点进行操作。以节点为中心,我们可以很容易得到节点的名字、值、属性、父亲节点以及叶子节点等相关的信息。但是,在一些特定的应用场合,这些 API 不能直接提供给我们所需要的功能。
比 如对上图中清单 1 所描述的课程,我们需要获得该课程的 cid,或者只需要该课程的老师名字 tearchername,或者需要获得该课程中所有学生的名字 name。在这样的情况下,我们只知道该 XML 文档中有一个 cid 的属性,或者有许多名字为 name 的节点,但是并不知道这些属性或者节点在 XML 树形结构的第几层上。因此,需要对整个文档进行搜索,直到找到需要的信息。这样的搜索可能在第一次找到匹配的元素时停止,像课程的 cid,因为它是唯一的;也有可能需要搜索全部的 XML 文档,像学生的名字 name,因为学生不只一个。像其它的 XML 解析器一样,JDOM 没有直接为我们提供满足这类需要的 API。
|
|
由上可知,通过 JDOM 的 API,我们可以方便的从一个节点得到其父亲节点或者叶子
节点。在这些 API 的基础上,我们可以构建出针对于 XML 文档这种树形结构的遍历搜索算法。以这种算法为核心,定制工具类,从而提供满足特定应用需要的方法。比如类似 AJAX DOM 解释中的 getElementsByTagName(String name)
这样的方法。
JDOM 的二进制版本可以在 JDOM 站点免费下载(参见 参考资料 )。你也可以下载源代码,自己编译。目前 JDOM 的最新版本是 JDOM1.0。笔者所使用的开发环境是 Eclipse Europa,此开发工具可以在 Eclipse 网站(参见 参考资料 )免费下载;JDK 是 1.6,你也可以在 Sun 的站点下载,并且在操作系统中配置好环境变量。在 Eclipse 中创建一个 JAVA 工程,在工程的 Build Path 中将 JODM 加入,这样就可以使用 JDOM 进行开发了。
笔者针对于应用构建的基于 JDOM 的工具主要由三个类组成。如下是三个类的 UML 关系图。JdomTool.java
定义了一组接口方法,提供了我们解决应用所需的一组抽象 API,隐藏具体方法的实现细节。当然这个接口仅提供了解决笔者应用中所遇到的问题的一些方法,并没有企图解决所有的问题,这些方法及其他们能提供的功能将在工具类接口小节中详细介绍。JdomToolImpl
依赖 JdomToolHelper
实现了 JdomTool
接口中定义的方法。JdomToolHelper
封装了相应搜索算法的实现,使得 JdomTool
可以将算法这一部分的工作委托到 JdomToolHelper
中。这样的设计只是为了向外提供工具统一的访问接口,并保证每个类对自己的职责负责。
下面详细介绍这三个类的代码实现。
3.2.1 工具类接口
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 文档,直到找到所需要的节点为止。
… 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 工具类接口的实现类
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 文件进行操作。
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 中运行上面的代码,笔者得到了如下的结果:
定制的工具类提供了一组更为方便的操作,解决了笔者应用中遇到的特定问题。当然读者可以根据这样的扩展思路,来定制满足自己需要的工具类,甚至可以通过更为优美的设计使得这些 API 具有良好的灵活性,来应对普遍的应用问题,达到代码更高程度的复用。
总结
这 篇文章主要是展示了如何使用开源的 JDOM 框架来扩展定制自己的 XML 处理工具,使其能够更好的解决自己所遇到的问题。JDOM 为 XML 文件的读写提供了容易使用、高效的 API,但是其不可能解决所有的问题。因此,对于其它一些 XML 解析的共性问题,我们可以扩展已有的 API 构造自己的 API。笔者提供的工具类,也是如此。希望这篇文章能够引导读者对 XML 文件处理以及 JODM 的使用有一个更好的认识。