DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> XML學習教程 >> XML詳解 >> 構建自己的輕量級XML DOM分析程序
構建自己的輕量級XML DOM分析程序
編輯:XML詳解     

XML正迅快速的成為數據存儲和交換的標准格式流行開來了。現在可用的完整的Java XML分析器非常龐大而且功能強大--但是實現這些強大的功能的同時也要消耗等量的資源。舉例來說,流行的apache Xerces-J分析器超過1.7 MB,而最新的完整的Sun JAXP(用於XML處理的Java應用編程接口)實現程序包超過了3MB。因此使用一個功能強大的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文件的表現也可能會需要很多種節點類型。

<parser>SimpleDOMParser</parser>


  DOM模型使用一個document類型節點來封裝整個XML文件,所以DOM使用三種不同的節點。通過把所有的DOM節點類型抽象成為一個單獨的類型SimpleElement來盡可能的簡化DOM模型。一個SimpleElement獲得一個XML元素的關鍵的信息,比如標識名、元素屬性和任何封裝的文本或者XML。此外,SimpleDOMParser不使用任何特殊的節點類型表示最高等級的文檔。結果是大大地簡化了DOM樹,使之只包含SimpleElement節點。

  代碼段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();
}
}

  定義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函數是一個執行兩個整數數組的深度比較的簡單程序。

  XML語法分析策略和SimpleDOMParser實現

  與一個正常的文本文檔不同的是,一個符號語法規則的XML文檔有一些獨特的特點,可以促進語法分析工作:

  在一個XML文檔中所有的標記都必須匹配。每個起始標記都必須有一個匹配的結束標記,除了當標記本身就是兩個起始和結束標記的時候,比如<parser/>就是<parser></parser>的簡易格式。標記和屬性名稱是大小寫敏感的。

  在一個XML文檔中所有的標記都必須正確地嵌套。XML標記不可以交叉嵌套。 例如:一個包含<t1><t2>... </t1></t2>的文檔就是錯誤的,因為結束標記</t1>出現在了結束標記</t2>之前。

  著眼於這些規則,SimpleDOMParser語法分析策略就應該遵循如下偽代碼所示的模式:

While Not EOF(Input XML Document)

Tag = Next tag from the document


LastOpenTag = Top tag in Stack



If Tag is an open tag

Add Tag as the child of LastOpenTag

Push Tag in Stack

Else

// 結束標記

If Tag is the matching close tag of LastOpenTag

Pop Stack

If Stack is empty

Parse is complete

End If

Else

// 無效標記嵌套

Report error

End If

End If

End While
  這算法的關鍵就是標記堆棧,它可以保存從輸入文件中獲得的但是沒有與它們結束標記匹配的起始標記。堆棧的頂部總是最後的一個起始標記。
除第一個標記以外,每個新的起始標記將是上一個起始標記的子標記。所以語法分析程序把新的標記添加為上一個起始標記的子標記,然後把它推到堆棧的頂部,它就成了最新的起始標記。 另一方面,如果輸入標記是一個結束標記,它必須匹配最後一個起始標記。 基於正確嵌套規則,一個不匹配結束標記會出現XML語法錯誤。 當結束標記匹配最後一個起始標記,語法分析程序從堆棧中彈出最後一個起始標記,因為對於這個標記的分析是完整的。這個處理繼續下去,直到堆棧為空為止。此時,你就完成了整個文檔的語法分析過程。代碼段2給出了SimpleDOMParser.parse方法的整個源代碼。

SimpleDOMParser.Java

package simpledomparser;


import Java.io.Reader;
import Java.io.IOException;
import Java.io.EOFException;
import Java.util.Stack;


public class SimpleDOMParser {
private static final int[] cdata_start = {'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '['};
private static final int[] cdata_end = {']', ']', '>'};

private Reader reader;
private Stack elements;
private SimpleElement currentElement;

public SimpleDOMParser() {
elements = new Stack();
currentElement = null;
}

public SimpleElement parse(Reader reader) throws IOException {
this.reader = reader;

skipPrologs();

while (true) {
int index;
String tagName;


String currentTag = readTag().trim();
if (currentTag.startsWith("</")) {
//結束標記
tagName = currentTag.substring(2, currentTag.length()-1);
//沒有起始標記
if (currentElement == null) {
throw new IOException("Got close tag '" + tagName +
"' without open tag.");
}

//結束標記與起始標記不匹配
if (!tagName.equals(currentElement.getTagName())) {
throw new IOException("Expected close tag for '" +
currentElement.getTagName() + "' but got '" +
tagName + "'.");
}

if (elements.empty()) {
//文本處理結束
return currentElement;
} else {
//彈出前面的起始標記
currentElement = (SimpleElement)elements.pop();
}
} else {
// 起始標記或者帶有起始標記和結束標記的標記
index = currentTag.indexOf(" ");
if (index < 0) {
// 不帶屬性的標記
if (currentTag.endsWith("/>")) {

tagName = currentTag.substring(1, currentTag.length()-2);
currentTag = "/>";
} else {
// 起始標記
tagName = currentTag.substring(1, currentTag.length()-1);
currentTag = "";
}
} else {
// 帶有屬性的標記
tagName = currentTag.substring(1, index);
currentTag = currentTag.substring(index+1);
}

//創建元素
SimpleElement element = new SimpleElement(tagName);

//分析屬性
boolean isTagClosed = false;
while (currentTag.length() > 0) {

currentTag = currentTag.trim();

if (currentTag.equals("/>")) {
//結束標記
isTagClosed = true;
break;
} else if (currentTag.equals(">")) {
//起始標記
break;
}

index = currentTag.indexOf("=");

if (index < 0) {
throw new IOException("Invalid attribute for tag '" +
tagName + "'.");
}

// 取得屬性名
String attributeName = currentTag.substring(0, index);
currentTag = currentTag.substring(index+1);

// 取得屬性值
String attributeValue;
boolean isQuoted = true;
if (currentTag.startsWith("\"")) {
index = currentTag.indexOf('"', 1);
} else if (currentTag.startsWith("'")) {
index = currentTag.indexOf('\'', 1);
} else {
isQuoted = false;
index = currentTag.indexOf(' ');
if (index < 0) {
index = currentTag.indexOf('>');
if (index < 0) {
index = currentTag.indexOf('/');
}
}
}

if (index < 0) {
throw new IOException("Invalid attribute for tag '" +
tagName + "'.");
}

if (isQuoted) {
attributeValue = currentTag.substring(1, index);
} else {
attributeValue = currentTag.substring(0, index);
}

// 添加屬性到新的元素中
element.setAttribute(attributeName, attributeValue);
currentTag = currentTag.substring(index+1);
}

// 讀取起始標記和結束標記之間的文本
if (!isTagClosed) {
element.setText(readText());
}

// 添加一個新的元素作為目前元素的子元素
if (currentElement != null) {
currentElement.addChildElement(element);
}

if (!isTagClosed) {
if (currentElement != null) {
elements.push(currentElement);
}

currentElement = element;
} else if (currentElement == null) {
// 在文檔中只有一個標記
return element;
}
}
}
}

private int peek() throws IOException {
reader.mark(1);
int result = reader.read();
reader.reset();

return result;
}

private void peek(int[] buffer) throws IOException {
reader.mark(buffer.length);
for (int i=0; i<buffer.length; i++) {
buffer[i] = reader.read();
}
reader.reset();
}

private void skipWhitespace() throws IOException {
while (Character.isWhitespace((char)peek())) {
reader.read();
}
}

private void skipProlog() throws IOException {
//跳過"<?" or "<!"
reader.skip(2);

while (true) {
int next = peek();

if (next == '>') {
reader.read();
break;
} else if (next == '<') {
skipProlog();
} else {
reader.read();
}
}
}

private void skipPrologs() throws IOException {
while (true) {
skipWhitespace();

int[] next = new int[2];
peek(next);

if (next[0] != '<') {
throw new IOException("Expected '<' but got '" + (char)next[0] + "'.");
}

if ((next[1] == '?') (next[1] == '!')) {
skipProlog();
} else {
break;
}
}
}

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();
}

private String readText() throws IOException {

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();
}

private boolean compareIntArrays(int[] a1, int[] a2) {
if (a1.length != a2.length) {
return false;
}

for (int i=0; i<a1.length; i++) {
if (a1[i] != a2[i]) {
return false;
}
}

return true;
}
}

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved