XML正迅快速的成为数据存储和交换的标准格式流行开来了。现在可用的完整的Java XML分析器非常庞大而且功能强大--但是实现这些强大的功能的同时也要消耗等量的资源。
因此使用一个功能强大的XML分析器可能过于浪费。如果配置环境是一个Java小程序或者是一个J2ME应用程序,网络带宽或者系统存储器的制约可能根本不能够使用完整的XML分析器。本文将告诉你如何构建一个轻量级的XML DOM分析程序。
开始编写SimpleDOMParser
SimpleDOMParser是一个使用Java写的高度简化和超轻量级的XML DOM分析器。 你可以将配置整个分析器配置为一个小于4KB的.jar文件。源程序还不到400行呢。
显然,使用这么小的代码,SimpleDOMParser将不支持XML域名空间,不能够理解多字符集编码或者以DTD文件或者schema验证文件;但是SimpleDOMParser能做的就是把符合语法规则的XML标记解析为一个类似于DOM的元素树,让你执行从XML格式化文本提取的数据的公共任务。
为什么使用DOM作为模型而不是SAX呢?这是因为DOM提供一个比SAX更加易用的程序接口。与SAX不同的是,当你把一个XML文件作为一个DOM树来处理的时候,这个文件内的所有的信息都是可以利用的。虽然SAX分析模型能够提供比DOM模型更加优异的性能和利用更少的存储空间,但是大部分开发者在使用SAX的时候都会发现他们自己正在创建一个完整的或者部分的DOM树。
使用SAX,一个应用程序每次只能处理一条标记。如果其它的标记内容在处理的过程中必须被用到,那你就必须在处理的整个过程保持一种全局状态。而保持全局状态正是DOM模型目的的精髓。但是许多小型的XML应用程序不需要完整的DOM模型。因此,SimpleDOMParser提供到标记名、层次和内容的访问,但是不涉及完整的W3C DOM的许多用不上的功能。
简化DOM模型
一个DOM树是由分析XML文件产生的结点组成。结点是一个XML实体的非存储表现。标准W3C DOM模型有几种类型的结点。 举例来说,一个文本结点表示在XML文件中的一段文本,一个元素结点表示XML文件而一个属性结点表示一个元素内部的属性名和值。
DOM是一个树,因为除了根或文件结点以外的每个结点都有一个父结点。举例来说,属性结点总是和一个元素结点相关联,而用来封装元素的起始标记和结束标记中的文本是映射到一个文本结点的。文本结点是元素结点的一个子节点。所以,即使很简单的XML文件的表现也可能会需要很多种节点类型。举例来说,图1表示下面XML文件的一个W3C DOM树形表示。
- <parser>SimpleDOMParser</parser>
正如你在图1中所看见的,DOM模型使用一个document类型节点来封装整个XML文件,所以DOM使用三种不同的节点。通过把所有的DOM节点类型抽象成为一个单独的类型SimpleElement来尽可能的简化DOM模型。一个SimpleElement获得一个XML元素的关键的信息,比如标识名、元素属性和任何封装的文本或者XML。此外,SimpleDOMParser不使用任何特殊的节点类型表示***等级的文档。结果是大大地简化了DOM树,使之只包含SimpleElement节点。图2表示了简化的DOM树。
代码段1给出了SimpleElement类的完整的源程序。
- public class SimpleElement {
- private String tagName;
- private String text;
- private HashMap attributes;
- private LinkedList childElements;
- public SimpleElement(String tagName) {
- this.tagName = tagName;
- attributes = new HashMap();
- childElements = new LinkedList();
- }
- public String getTagName() {
- return tagName;
- }
- public void setTagName(String tagName) {
- this.tagName = tagName;
- }
- public String getText() {
- return text;
- }
- public void setText(String text) {
- this.text = text;
- }
- public String getAttribute(String name) {
- return (String)attributes.get(name);
- }
- public void setAttribute(String name, String value) {
- attributes.put(name, value);
- }
- public void addChildElement(SimpleElement element) {
- childElements.add(element);
- }
- public Object[] getChildElements() {
- return childElements.toArray();
- }
- }
#p#
定义XML语法分析基本元素
为了把一个XML文件处理成为上面提到的简化的DOM树模型,我们必须定义一些基本的语法分析规则。使用这些规则,语法分析程序就能容易地从输入的XML文件中提取标记或者文本块。
***个是peek,从输入的XML文件中返回下一个字符,而实际上则不必从下层流中获得这个字符。通过保持输入流的完整性,高级函数比如readTag和readText(后面将介绍)可以更加容易地根据它们接下来期待的字符获取需要的内容。
- private int peek() throws IOException {
- reader.mark(1);
- int result = reader.read();
- reader.reset();
- return result;
- }
下一个方法是skipWhitespce,作用是跳过输入的XML流中的空格、制表符或者回车符。
- private void skipWhitespace() throws IOException {
- while (Character.isWhitespace((char) peek())) {
- reader.read();
- }
- }
在创建了如上所述的这两个方法后,我们就可以写一个函数从输入文件中检索XML标记。
- private String readTag() throws IOException {
- skipWhitespace();
- StringBuffer sb = new StringBuffer();
- int next = peek();
- if (next != '<') {
- throw new IOException
- ("Expected > but got " + (char) next);
- }
- sb.append((char)reader.read());
- while (peek() != '>') {
- sb.append((char)reader.read());
- }
- sb.append((char)reader.read());
- return sb.toString();
- }
和peek方法联合使用,readTag函数只获得一个标记的内容,而让别的函数去处理其他的内容。 ***的一个方法是readText函数,用来读取XML标记之间的文本。
- private String readText() throws IOException {
- int[] cdata_start = {'<', '!',
- '[', 'C', 'D', 'A', 'T', 'A', '['};
- int[] cdata_end = {']', ']', '>'};
- StringBuffer sb = new StringBuffer();
- int[] next = new int[cdata_start.length];
- peek(next);
- if (compareIntArrays(next, cdata_start) == true) {
- // CDATA
- reader.skip(next.length);
- int[] buffer = new int[cdata_end.length];
- while (true) {
- peek(buffer);
- if (compareIntArrays
- (buffer, cdata_end) == true) {
- reader.skip(buffer.length);
- break;
- } else {
- sb.append((char)reader.read());
- }
- }
- } else {
- while (peek() != '<') {
- sb.append((char)reader.read());
- }
- }
- return sb.toString();
- }
这次使用的peek方法是前面那个从基本的XML文档返回一个字符串序列的peek方法的变体。这个peek变体让语法分析程序判断它将分析的文本是否被装入一个CDATA块。compareIntArrays函数是一个执行两个整数数组的深度比较的简单程序。
#p#
定义XML语法分析基本元素
为了把一个XML文件处理成为上面提到的简化的DOM树模型,我们必须定义一些基本的语法分析规则。使用这些规则,语法分析程序就能容易地从输入的XML文件中提取标记或者文本块。
***个是peek,从输入的XML文件中返回下一个字符,而实际上则不必从下层流中获得这个字符。通过保持输入流的完整性,高级函数比如readTag和readText(后面将介绍)可以更加容易地根据它们接下来期待的字符获取需要的内容。
- private int peek() throws IOException {
- reader.mark(1);
- int result = reader.read();
- reader.reset();
- return result;
- }
下一个方法是skipWhitespce,作用是跳过输入的XML流中的空格、制表符或者回车符。
- private void skipWhitespace() throws IOException {
- while (Character.isWhitespace((char) peek())) {
- reader.read();
- }
- }
在创建了如上所述的这两个方法后,我们就可以写一个函数从输入文件中检索XML标记。
- private String readTag() throws IOException {
- skipWhitespace();
- StringBuffer sb = new StringBuffer();
- int next = peek();
- if (next != '<') {
- throw new IOException
- ("Expected > but got " + (char) next);
- }
- sb.append((char)reader.read());
- while (peek() != '>') {
- sb.append((char)reader.read());
- }
- sb.append((char)reader.read());
- return sb.toString();
- }
和peek方法联合使用,readTag函数只获得一个标记的内容,而让别的函数去处理其他的内容。 ***的一个方法是readText函数,用来读取XML标记之间的文本。
- private String readText() throws IOException {
- int[] cdata_start = {'<', '!',
- '[', 'C', 'D', 'A', 'T', 'A', '['};
- int[] cdata_end = {']', ']', '>'};
- StringBuffer sb = new StringBuffer();
- int[] next = new int[cdata_start.length];
- peek(next);
- if (compareIntArrays(next, cdata_start) == true) {
- // CDATA
- reader.skip(next.length);
- int[] buffer = new int[cdata_end.length];
- while (true) {
- peek(buffer);
- if (compareIntArrays
- (buffer, cdata_end) == true) {
- reader.skip(buffer.length);
- break;
- } else {
- sb.append((char)reader.read());
- }
- }
- } else {
- while (peek() != '<') {
- sb.append((char)reader.read());
- }
- }
- return sb.toString();
- }
这次使用的peek方法是前面那个从基本的XML文档返回一个字符串序列的peek方法的变体。这个peek变体让语法分析程序判断它将分析的文本是否被装入一个CDATA块。 compareIntArrays函数是一个执行两个整数数组的深度比较的简单程序。
【编辑推荐】