我和XML的亲密接触(曾庆斌)
XML作为全球通用的结构化语言,已经在各种开发平台(比如Microsoft Studio系列、Oracle系列、Inprise Borland系列等)得到了很好的支持 。偶由于第一次教XML基础把我学习过程中的体会写下来和同事朋友共享,假期我和我的朋友请教XML在软件企业的应用情况,他告诉我应用的比较广泛,他是从事的电子政务开发较早的引入了XML,所以他告诉我,尝到了许多甜头,在许多项目中利用XML数据交换信息,省去了许多麻烦事,不用制定繁锁的数据格式,利用XML数据易于表达,也利于一线开发者跟踪调试。
通过学习我认为:在XML应用中,最常用也最实用的莫过于XML文件的读写,也是学生必须掌握开发技能之一,也是难点之一,还要了解XML的解析器,目前应用比较多的XML解释器还有Apache Xexces、Apache Crimson、JDOM、dom4j、Electric XML以及XML Pull Parser。不管使用那种解析器,读写XML文件要实现三种基本操作:
1)根据输入流构建文档:
2)遍历元素和内容,并做一些更改:
l 从文本内容中除去前导和尾随的空白。
l 如果结果文本内容为空,就删除它。
l 否则,将它包装到父元素的名称空间中一个名为“text”的新元素中。
3)将已修改的文档写入输出流:
1)根据输入流构建文档
l Xerces DOM解析器代码
A) 构造一个输入流
BufferedReader in = new BufferedReader(new FileReader(“student.xml”));
B)实例化一个XML解释器对象
DOMParser parser = new DOMParser();
C) 使用XML解释器对象方法parser方法把XML文件注入到DOCument中
Parser.parser(new InputSouece(in));
D) 从解释器对象中取出装入student.xml的DOCument对象
DOMcment doc = parser.getDocument();
l Crimson DOM 解析器代码
A) 构造一个输入流
BufferedReader in = new BufferedReader(new FileReader(“student.xml”));
B) 设置系统特性来选择要构造的 DOM 表示的构建器工厂类
System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
"org.apache.crimson.jaxp.DocumentBuilderFactoryImpl");
仅当想选择一个要由 JAXP 使用的特定 DOM 时,才需要这一步;否则,它使用缺省实现。
C) 创建构建器工厂的实例
DocumentBuilderFactory dbf = DocumentBuilderFactoryImpl.newInstance();
dbf.setNamespaceAware(true); //启用名称空间支持
DocumentBuilder builder = dbf.newDocumentBuilder();
D) 方法parser方法把XML文件注入到DOCument中并
从解释器对象中取出装入student.xml的DOCument对象
Document doc = builder.parse(in);
l JDOM 解析器代码
A) 构造一个输入流
BufferedReader in = new BufferedReader(new FileReader(“student.xml”));
B)实例化一个XML解释器对象
SAXBuilder builder = new SAXBuilder(false); //参数值禁止验证的 SAXBuilder
C) 使用方法parser方法把XML文件注入到DOCument中并
从解释器对象中取出装入student.xml的DOCument对象
Document doc = builder.build(in);
2)遍历元素和内容,并做一些更改:
要想遍历和内容DOMcment中的节点,必须了解DOM的结构。实际上XML将数据组织成为一棵树,DOM通过解析XML文档,为XML文档在逻辑上建立一个树模型,树的节点是一个个的对象。这样通过操作这棵树和这些对象就可以完成对XML文档的操作,为处理文档的所有方面提供了一个完美的概念性框架。 如下XML文档:<line id=” 1” > the <bold>First</bold>line</line>
DOM为上述XML文档创建的结构表示如下
DOCument |
Element line
|
Attr id |
Text 1 |
Text the |
Element bold |
Text line |
Text First |
DOCument |
Element line
|
Attr id |
Text 1 |
Text the |
Element bold |
Text line |
Text First |
DOCument |
Element line
|
Attr id |
Text 1 |
Text the |
Element bold |
Text line |
Text First |
注意:
1. DOM树的根结点为Document,不同于对应XML文档的根元素,XML文档的根元素对应的元素结点是Document结点的一个子结点。
2. 属性结点的值,不包含子元素的元素的元素内容,都构成一个文本结点,分别充当属性结点 和元素结点的一个孩子(child)。
3. 属性结点只是附属在所在元素对应的元素结点下,它们之间并不构成“父子关系”,即调用元素结点的getChildNodes()方法,并不能返回属性结点。
4. 兄弟结点:Dom树中,每个结点下面的多个孩子结点构成兄弟关系。
由于DOM“一切都是节点(everything-is-a-node)”,XML树的每个 Document、Element、Text 、Attr和Comment都是 DOM Node。
由上面例子可知, DOM 实质上是一些节点的集合。由于文档中可能包含有不同类型的信息,所以定义了几种不同类型的节点,如:Document、Element、Text、Attr 、CDATASection、ProcessingInstruction、Notation 、EntityReference、Entity、DocumentType、DocumentFragment等。
表:不同类型节点说明
Interface | 说明 | nodeName | nodeValue | attributes |
Attr | 表示 | 与 Attr.name 相同 | 与 Attr.value 相同 | null |
CDATASection | 用于转义文本块 | "#cdata-section" | 与 CharacterData.data 相同,CDATA 节的内容 | null |
Comment | 表示注释的内容,即起始 ' | "#comment" | 与 CharacterData.data 相同,该注释的内容 | null |
Document | 表示整个 HTML 或 XML 文档。从概念上讲,它是文档树的根,并提供对文档数据的基本访问 | "#document" | null | null |
DocumentFragment | 文档树的一部分或创建文档的新片段 | "#document-fragment" | null | null |
DocumentType |
| 与 DocumentType.name 相同 | null | null |
Element | 表示 HTML 或 XML 文档中的一个元素 | 与 Element.tagName 相同 | null | NamedNodeMap |
Entity | 表示在 XML 文档中解析和未解析的已知实体。注意,这模仿该实体本身而不是 实体声明 | entity name | null | null |
EntityReference | 用来在树中表示实体引用 | 引用的实体名称 | null | null |
Notation | 表示在 DTD 中声明的表示法 | notation name | null | Null |
ProcessingInstruction | 表示“处理指令” | 与 ProcessingInstruction.target 相同 | 与 ProcessingInstruction.data 相同 | null |
Text | 表示 | "#text" | 与 CharacterData.data 相同,该文本节点的内容 | null |
在创建XML文件时,如定义如下的XML文档:
<?xml version="1.0" encoding="UTF-8"?>
<students>
<!--this is an example-->
<student>
<name>
<first-name>Mike</first-name>
<last-name>Silver</last-name>
</name>
<sex>male</sex>
<class studentid="15">98211</class>
<birthday>
<day>3</day>
<month>3</month>
<year>1979</year>
</birthday>
</student>
<student>
<name>
<first-name>Ben</first-name>
<last-name>Silver</last-name>
</name>
<sex>male</sex>
<class studentid="16">98211</class>
<birthday>
<day>3</day>
<month>3</month>
<year>1980</year>
</birthday>
</student>
</students>
我们很自然想象到能得到如下图的结构,但是这只是数据的描述,而不是DOM树的结构。
Element students |
Text comment
|
Element student |
Element student |
Element name |
Element sex |
Element class |
Element birthday |
Element L_name |
Element month |
Element day |
Element year |
Text male L_name |
Text Mike L_name |
Text Silver L_name |
Text 98211 |
Element F_name |
Attr id |
Attr 15 |
Text 3 L _name |
Text 3 L _name |
Text 1979 L _name |
从上述图中我们可以看出students包括三个元素,两个student和一个注解元素,但实际情况和上述描述的完全不一样,在DOM中节点和元素不是等价的,它的7个节点包括:两个student元素、注释及它们周围的文本节点。这些文本节点有可能是回车换行、空格或者退格,假如把这些回车换行、空格和退格都删除,那么DOM解释的时候就没有这些文本节点,孩子节点就真的只有3个了。下图是DOM树的精确描述:
Element students |
Text CR,LT,TAB |
Element student |
Element student |
Element name |
Element sex |
Element class |
Element birthday |
Element L_name |
Element month |
Element day |
Element year |
Text male L_name |
Text Mike L_name |
Text Silver L_name |
Text 98211 |
Element F_name |
Attr id |
Attr 15 |
Text 3 L _name |
Text 3 L _name |
Text 1979 L _name |
Text comment |
Text CR,LT,TAB |
Text CR,LT,TAB |
Text CR,LT,TAB |
通过上述分析,我们就可以编写下面的方法遍历和修改DOM
protected void modifyElement(Element element) {
2 // loop through child nodes
3 Node child;
4 Node next = (Node)element.getFirstChild();
5 while ((child = next) != null) {
6 // 取子节点
7 next = child.getNextSibling();
8 //是文本节点(内容)吗
9 if (child.getNodeType() == Node.TEXT_NODE) {
10 // 滤除空格
11 String trimmed = child.getNodeValue().trim();
12 if (trimmed.length() == 0) {
13 // 是空内容删除
14 element.removeChild(child);
15 } else {
16 //建立一个Text元素
17 Document doc = element.getOwnerDocument();
18 String prefix = element.getPrefix();
19 String name = (prefix == null) ? "text" : (prefix + ":text");
20 Element text =
21 doc.createElementNS(element.getNamespaceURI(), name);
22
23 text.appendChild(doc.createTextNode(trimmed));
24 element.replaceChild(text, child);
25 }
26 } else if (child.getNodeType() == Node.ELEMENT_NODE) {
27 // handle child elements with recursive call
28 modifyElement((Element)child);
29 }
30 }
31 }
我们现在应用XML标记语言开发一个学生管理系统,系统中有一个学生花名册,可以在任何文本编辑器中先建立如下结构的XML文件,类似于HTML结构,但XML语义比较严格,起始标记必须配对,比如"〈学生花名册〉"与"〈/学生花名册〉"对应,空格多少可不必在意,但一般都以缩格形式书写,便于阅读。把此文件命名为Student.xml,可以在任何支持XML的浏览器中打开测试一下,如果输入正确,在浏览中可以看到此文件的树形表示结构。
<?xml version="1.0" encoding="GB2312" ?>
<学生花名册>
<学生 性别 = "男">
<姓名>李华</姓名>
<年龄>14</年龄>
<电话>6287555</电话>
</学生>
<学生 性别 = "男">
<姓名>张三</姓名>
<年龄>16</年龄>
<电话>8273425</电话>
</学生>
</学生花名册>
上述文件被装入DOM中形成下列的树结构
Element 学生花名册 |
Element 学生 |
Element 学生 |
Text 回车、换行 |
Text 回车、换行 |
Text 回车、换行 |
Text 回车、换行 |
Attr 性别 |
Text 男 |
Text 回车、换行 |
Element 姓名 |
Text 李华 |
Text 回车、换行 |
Element 年龄 |
Text 14 |
Text 回车、换行 |
Element 电话 |
Text 6287655 |
Text 回车、换行 |
Element Document |
解决方案
Student.xml |
DOM |
DOMcment |
学生花名册 |
学生 |
学生 |
Vector |
StudentBean |
StudentBean |
准备工作做完后,接着就开始写实质性的JAVA代码了。为保存从XML文件读入的信息,需要先建一个简单的Bean来保存学生信息,命名为StudentBean,代码如下所示:
public class StudentBean
{
private String sex; //学生性别
private String name; //学生姓名
private int age; //学生年龄
private String phone; //电话号码
public void setSex(String s)
{
sex = s;
}
public void setName(String s)
{
name = s;
}
public void setAge(int a)
{
age = a;
}
public void setPhone(String s)
{
phone = s;
}
public String getSex()
{
return sex;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String getPhone()
{
return phone;
}
}
上述JavaBean建立好之后写XML的测试类,我把这个类命名为XMLTest,为了读写XML文件,需要导入如下JAVA包,"//"后为注释说明, XML解释器用Apache的Crimson,可以到Apache主页去上载,
import java.io.*; //Java基础包,包含各种IO操作
import java.util.*; //Java基础包,包含各种标准数据结构操作
import javax.xml.parsers.*; //XML解析器接口
import org.w 3c .dom.*; //XML的DOM实现
import org.apache.crimson.tree.XmlDocument; //写XML文件要用到
为了保存多个学生信息,还得借助一个集合类(并不是单纯意义上的集合,JAVA中的集合是集合框架的概念,包含向量、列表、哈希表等),这里采用Vector向量类。定义在XMLTest测试类中,命名为student_Vector。然后定义两个方法readXMLFile和writeXMLFile,实现读写操作。
代码如下:
private void readXMLFile(String inFile) throws Exception ...{
//为解析XML作准备,创建DocumentBuilderFactory实例,指定DocumentBuilder
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try ...{
db = dbf.newDocumentBuilder();
}catch (ParserConfigurationException pce) ...{
System.err.println(pce); //出异常时输出异常信息,然后退出,下同
System.exit(1);
}
Document doc = null;
try ...{
doc = db.parse(inFile);
} catch (DOMException dom) ...{
System.err.println(dom.getMessage());
System.exit(1);
} catch (IOException ioe) ...{
System.err.println(ioe);
System.exit(1);
}
//下面是解析XML的全过程,比较简单,先取根元素"学生花名册"
Element root = doc.getDocumentElement();
//取"学生"元素列表
NodeList students = root.getElementsByTagName("学生");
for (int i = 0; i < students.getLength(); i++) ...{
//依次取每个"学生"元素
Element student = (Element) students.item(i);
//创建一个学生的Bean实例
StudentBean studentBean = new StudentBean();
//取学生的性别属性
studentBean.setSex(student.getAttribute("性别"));
//取"姓名"元素,下面类同
NodeList names = student.getElementsByTagName("姓名");
if (names.getLength() == 1) ...{
Element e = (Element) names.item(0);
Text t = (Text) e.getFirstChild();
studentBean.setName(t.getNodeValue());
}
NodeList ages = student.getElementsByTagName("年龄");
if (ages.getLength() == 1) ...{
Element e = (Element) ages.item(0);
Text t = (Text) e.getFirstChild();
studentBean.setAge(Integer.parseInt(t.getNodeValue()));
}
NodeList phones = student.getElementsByTagName("电话");
if (phones.getLength() == 1) ...{
Element e = (Element) phones.item(0);
Text t = (Text) e.getFirstChild();
studentBean.setPhone(t.getNodeValue());
}
student_Vector.add(studentBean);
}
}
private void writeXMLFile(String outFile) throws Exception ...{
//为解析XML作准备,创建DocumentBuilderFactory实例,指定DocumentBuilder
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try ...{
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) ...{
System.err.println(pce);
System.exit(1);
}
Document doc = null;
doc = db.newDocument();
//下面是建立XML文档内容的过程,先建立根元素"学生花名册"
Element root = doc.createElement("学生花名册");
//根元素添加上文档
doc.appendChild(root);
//取学生信息的Bean列表
for (int i = 0; i < student_Vector.size(); i++) ...{
//依次取每个学生的信息
StudentBean studentBean = (StudentBean) student_Vector.get(i);
//建立"学生"元素,添加到根元素
Element student = doc.createElement("学生");
student.setAttribute("性别", studentBean.getSex());
root.appendChild(student);
//建立"姓名"元素,添加到学生下面,下同
Element name = doc.createElement("姓名");
student.appendChild(name);
Text tName = doc.createTextNode(studentBean.getName());
name.appendChild(tName);
Element age = doc.createElement("年龄");
student.appendChild(age);
Text tAge = doc.createTextNode(String.valueOf(studentBean.getAge()));
age.appendChild(tAge);
Element phone = doc.createElement("电话");
student.appendChild(phone);
Text tPhone = doc.createTextNode(studentBean.getPhone());
phone.appendChild(tPhone);
}
//把XML文档输出到指定的文件
FileOutputStream outStream = new FileOutputStream(outFile);
OutputStreamWriter outWriter = new OutputStreamWriter(outStream);
((XmlDocument) doc).write(outWriter, "GB2312");
outWriter.close();
outStream.close();
}
最后加入测试主函数,如下:
public static void main(String[] args) throws Exception ...{
//建立测试实例
XMLTest xmlTest = new XMLTest(); //初始化向量列表
xmlTest.student_Vector = new Vector();
System.out.println("开始读Input.xml文件");
xmlTest.readXMLFile("Input.xml");
System.out.println("读入完毕,开始写Output.xml文件");
xmlTest.writeXMLFile("Output.xml");
System.out.println("写入完成");
}
好了,保存好StudentBean和XMLTest,把Input.xml保存到工作目录下。如果您输入很仔细,没敲错字母的话,可以看到"写入完成"了,去瞧瞧Output.xml文件和Input.xml文件是不是一样吧。