我和XML的亲密接触

来源:互联网 发布:js小球与边框碰撞反弹 编辑:程序博客网 时间:2024/04/28 13:48

我和XML的亲密接触(曾庆斌)

XML作为全球通用的结构化语言,已经在各种开发平台(比如Microsoft Studio系列、Oracle系列、Inprise Borland系列等)得到了很好的支持 。偶由于第一次教XML基础把我学习过程中的体会写下来和同事朋友共享,假期我和我的朋友请教XML在软件企业的应用情况,他告诉我应用的比较广泛,他是从事的电子政务开发较早的引入了XML,所以他告诉我,尝到了许多甜头,在许多项目中利用XML数据交换信息,省去了许多麻烦事,不用制定繁锁的数据格式,利用XML数据易于表达,也利于一线开发者跟踪调试。 

  通过学习我认为:在XML应用中,最常用也最实用的莫过于XML文件的读写,也是学生必须掌握开发技能之一,也是难点之一,还要了解XML的解析器,目前应用比较多的XML解释器还有Apache XexcesApache CrimsonJDOMdom4jElectric 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.xmlDOCument对象

          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.xmlDOCument对象

         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.xmlDOCument对象

      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树的每个 DocumentElementText AttrComment都是 DOM Node

由上面例子可知, DOM 实质上是一些节点的集合。由于文档中可能包含有不同类型的信息,所以定义了几种不同类型的节点,如:DocumentElementTextAttr CDATASectionProcessingInstructionNotation EntityReferenceEntityDocumentTypeDocumentFragment等。

表:不同类型节点说明

Interface

说明

nodeName

nodeValue

attributes

Attr

表示 Element 对象中的属性

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 节点为只读的

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

表示 Element Attr 的文本内容

"#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解释器用ApacheCrimson,可以到Apache主页去上载,

import java.io.*;            //Java基础包,包含各种IO操作 
import java.util.*;          //Java基础包,包含各种标准数据结构操作 
import javax.xml.parsers.*;  //XML解析器接口 
import org.w3c.dom.*;        //XMLDOM实现 
import org.apache.crimson.tree.XmlDocument;   //XML文件要用到 

为了保存多个学生信息,还得借助一个集合类(并不是单纯意义上的集合,JAVA中的集合是集合框架的概念,包含向量、列表、哈希表等),这里采用Vector向量类。定义在XMLTest测试类中,命名为student_Vector。然后定义两个方法readXMLFilewriteXMLFile,实现读写操作。

代码如下: 

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("写入完成"); 

} 

好了,保存好StudentBeanXMLTest,把Input.xml保存到工作目录下。如果您输入很仔细,没敲错字母的话,可以看到"写入完成"了,去瞧瞧Output.xml文件和Input.xml文件是不是一样吧。