綜述:可擴展標註語言(eXtensible Markup Language,XML)正被迅速的運用於業界,它已作為與平台、語言和協定無關的格式描述和交換資料的廣泛應用標準。 XML和它的輔助規範可用於描述資料的文檔表現,描述XML文檔類型的限制,描述XML文檔和資源之間的鏈接,描述XML文檔的自動轉換和格式化。
如何開發自訂標籤庫?
我使用JSP和ASP程式已經有一段頗長的時間了,在兩種伺服器端的程式設計方式中,我越來越覺得JSP的功能強大得多。不提別的,其中JSP的標籤庫就是我選擇JSP作為首選伺服器端Web應用開發工具的原因。為什麼?因為:維護和開發的速度。在一個單一的伺服器頁面中,你可以混合使用各種不同的腳本方法和物件。就?quot;混凝土"一樣,這種混合可令伺服器端的腳本變得強大,並且讓伺服器端的程式設計者設計出非常靈活和動態的Web頁面。不過這種自由的混合也有其缺點,那就是維護起來非常麻煩,特別是當專案逐漸變大時。就會變慢,不利於開發中等和大型的Web應用,一旦開發完,網站還要找合格的程式設計者來維護這些相當複雜的程式碼。
幸好,JSP提供了一個很好解決的方法。標籤庫提供了一個簡單的方法來建立一個可重複使用的程式碼區塊。一旦標籤庫設計好,它就可以在許多專案中再次使用。更方便的是,與COM和J2EE不同,你無需學習任何其它的技巧就可以建立一個標籤庫!只要你懂得寫JSP,你就可以建立一個標籤庫。標籤庫還可以改善Web應用的維護。這個是得益於JSP頁面自訂標籤的簡單XML介面。這樣,Web設計者甚至可以做到無需知道任何JSP的知識,就可以建立JSP的Web應用。這個開放式的Web開發對於團隊運作是非常有效的。 JSP程式設計者可以建立自訂的標籤和後台的程式碼模組,而Web設計者可以使用自訂的標籤來建立Web應用,並且將精力集中在Web設計上。
1. 標籤庫的定義JSP標籤庫(也稱為自訂庫)可看成是一套產生基於XML腳本的方法,它經由JavaBeans來支援。在概念上說,標籤庫是非常簡單且可以重複使用的程式碼建構。
執行XML/XSL轉換的標籤範例和HTML頁面
<%@ taglib uri=" http://www.jspinsider.com/jspkit/JAXP " prefix="JAXP"%>
c:/xml/example.xml
c:/xml/example.xsl
在這個例子中,透過使用簡單的標籤來存取後台更為強大的程式碼,一個XML被裝載,並且透過一個XSL檔案來產生一個結果,並發送給客戶端,全部透過使用一個簡單的標籤呼叫就做到了。
自訂標籤為在JSP專案中創建易於重用的程式碼打開了一扇大門。你所需要的只是標籤庫和它的文件說明。
2. 標籤的元件雖然標籤庫非常容易使用,不過要建立一個內裡的設計來支援標籤庫是相當複雜的,起碼要比建立一個簡單的JavaBean複雜。這個複雜是來自於標籤庫是由幾個部分構成的。不過,你只要知道Java和JSP的知識就夠了。
一個簡單的標籤由下面的元素構成:
⑴ JavaBeans:為了得到Java與生俱來的物件導向的好處,可重複使用的程式碼應該放到一個獨立的程式碼容器中。這些JavaBeans並不是標籤庫的一部分。不過它是你的程式碼庫用來執行相關任務的基本程式碼區塊。
⑵ 標籤處理:這是標籤庫的真正核心。一個標籤處理器將引用它需要的任何資源(你的JavaBeans)和存取你的JSP頁面的全部資訊(pageContext物件)。 JSP頁面也會將所有已經設定好的標籤屬性和JSP頁面上的標籤體中的內容傳送給標籤處理器。在標籤處理器處理完畢後,它將發回輸出到你的JSP頁面進行處理。
⑶ 標籤庫的描述(tld文件):這是一個簡單的XML文件,它記錄標籤處理器的屬性、資訊和位置。 JSP容器透過這個檔案來得知從哪裡及如何呼叫一個標籤庫。
⑷ 網站的web.xml檔案:這是你網站的初始化文件,在這個檔案中,你定義了網站中用到的自訂標籤,以及哪個tld檔案用來描述每個自訂的標籤。
⑸ 分發檔案(一個WAR或JAR檔案):如果你想重複使用自訂標籤的話,你需要一個方法來將它由一個專案轉移到另一個專案中。將標籤庫打包為一個JAR檔案是一個簡單且有效的方式。
⑹ 在你的JSP檔案中作標籤庫聲明:很簡單,如果要用到該標籤的話,只要在頁面聲明一下就可以,其後,你就可以在該JSP頁面的任何地方使用它。
看來要做的工作很多,但其實不是很難。它的要點並不在於編碼,而是如何將各部分正確地組織起來。不過,這樣的分層是很重要的,它可令標籤的使用靈活且更容易轉移。更重要的是,這些層的存在可讓處理建立標籤的工程透過一個JSP IDE(JSP的整合開發環境)自動完成。期望將來的JSP IDE可自動完成創建一個自訂標籤的大部分工作,這樣你只需要寫程式碼和標籤處理就可以了。
注意:一個標籤處理僅定義一個自訂標籤;一個標籤庫是幾個處理相同任務的標籤處理器的集合。
3. 建立自己的標籤以下將一步一步教你如何建立自訂的標籤,具體的例子是擴充JSP,令它擁有自己的HTML編碼功能。這個功能將所有的<和>字元用HTML程式碼來取代。它可以很容易地擴展為做其它的編碼處理。為了簡化,這個例子只解釋了建立自訂標籤的基本要素。
⑴ 建立一個JavaBean
你程式碼中任何可重複使用的部分都應該放到一個JavaBean中。這個很重要,因為你要經常在專案的其它地方用到這些程式碼。放在標籤處理器中的任何程式碼在標籤外都是不可以重新使用的,因此將可重複使用的程式碼部分獨立開來是很重要的。在這個例子總,為HTML編碼的邏輯是常用的,因此放到JavaBean中。
⑵ HTML編碼JavaBean
/* HTML_Format.Java */
public class HTML_Format extends Object implements Java.io.Serializable {
/** 建立新的HTML_Format */
public HTML_Format() {}
/** 將一個字串中所有的所有< 和> 字元用回應的HTML編碼取代*/
public String HTML_Encode(String as_data)
{
int li_len = as_data.length();
/*string buffer的長度要比原來的字串長*/
StringBuffer lsb_encode = new StringBuffer(li_len + (li_len/10));
/* 循環替換全部的< 和> 字元*/
為( int li_count = 0 ; li_count < li_len ; li_count++)
{ String ls_next = String.valueOf(as_data.charAt(li_count));
if (ls_next.equals("<")) ls_next = "<";
if (ls_next.equals(">")) ls_next = ">";
lsb_encode.append( ls_next );
}
return( lsb_encode.toString() );
}
}
⑶ 建立一個標籤處理器標籤處理器使用以下的程式碼:
HTML編碼標籤處理器
import Java.io.IOException;
import Javax.servlet.jsp.*;
import Javax.servlet.jsp.tagext.*;
public class HTML_FormatTag extends BodyTagSupport
{
/* 1} 在標籤末將會呼叫這個函數*/
public int doEndTag() throws JspTagException
{
try
{ /* 2}得到標籤中的文字*/
BodyContent l_tagbody = getBodyContent();
String ls_output = "";
/* 3}如果標籤體有文本,就處理它*/
if(l_tagbody != null)
{ HTML_Format l_format = new HTML_Format();
/* 3a} 將標籤體的內容轉換為一個字串*/
String ls_html_text = l_tagbody.getString();
ls_output = l_format.HTML_Encode(ls_html_text);
}
/* 4}將結果寫回資料流*/
pageContext.getOut().write(ls_output.trim());
}
catch (IOException e)
{ throw new JspTagException("Tag Error:" + e.toString());
}
/* 讓JSP繼續處理以下頁面的內容*/
return EVAL_PAGE;
}
}
這個處理很簡單,它包含有:
o 讀入標籤開始和結束間的文字o 呼叫html編碼函數o 傳回結果到JSP頁面。
⑷ 建立一個標籤描述器需要描述自訂標籤以讓系統知道如何處理。此描述檔的後綴為.tld,通常它的名字和標籤處理器相同,並存放在"/WEB-INF/"目錄中。
HTML編碼標籤描述器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
" http://Java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd ">
<TAGLIB>
<TLIBVERSION>1.0</TLIBVERSION>
<JSPVERSION>1.1</JSPVERSION>
<SHORTNAME>HTML_FormatTag</SHORTNAME>
<URI></URI>
<INFO>HTML Encoding Tag </INFO>
<TAG>
<NAME>HTMLEncode</NAME>
<TAGCLASS>HTML_FormatTag</TAGCLASS>
<INFO>Encode HTML</INFO>
</TAG>
</TAGLIB>
⑸ 更新Web XML檔案現在可告訴JSP容器使用標籤庫。為此要修改web.xml文件,具體來說是要在其中加入一個taglib的專案來註冊該標籤庫。最重要的是,要為tag分配一個URI。 URI是一個唯一的引用,只應用在該網站的這個特別的標籤上。使用全長的URL或套件名稱是一個好的習慣,它可以確保唯一性,因為該標籤可以在不同的網站上使用。這個例子是簡化了。
修改web.xml文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
" http://Java.sun.com/j2ee/dtds/web-app_2.2.dtd ">
<WEB-APP>
<TAGLIB>
<TAGLIB-URI>
HTMLEncode
</TAGLIB-URI>
<TAGLIB-LOCATION>
/WEB-INF/HTML_FormatTag.tld
</TAGLIB-LOCATION>
</TAGLIB>
</WEB-APP>
⑹ 使用新的標籤自訂的標籤已經設定好,可以用在一個JSP頁面上。要做到這一點,只需在該頁面使用taglib指示命令聲明一下該標籤就可以了,該標籤通過它唯一的URI被引用,並且會被分配一個名字空間前綴。前綴可以任意,只要它不與其它的名字空間衝突便可。
在一個JSP頁面上使用HTML編碼標籤:
<%@ taglib uri="HTMLEncode" prefix="Examples" %>
<PRE>
<?XML:NAMESPACE PREFIX = 範例 /><Examples:HTMLEncode>
< Hello , Simple sample >
</Examples:HTMLEncode>
</PRE>
範例程式碼的輸出
< Hello , Simple sample >
which displays as:
< Hello , Simple sample >
透過這個標籤,我就將該頁面的所有程式碼編碼了。有趣的是所有的自訂標籤都是在伺服器上處理的。這意味著你將不會在輸出的頁面上看到自訂的標籤。
建立一個標籤不是很難。最困難的部分是要學習標籤處理的所有細節。這是一個很強大的功能,我們只是提到了最基本的地方。由於這個處理需要幾步,新的JSP編程者在創建標籤時將會感到困惑。
如何利用JSP開發DOM應用?
DOM是Document Object Model的縮寫,即文件物件模型。 XML將資料組織為一顆樹,所以DOM就是對這棵樹的一個物件描敘。通俗的說,就是透過解析XML文檔,為XML文檔在邏輯上建立一個樹模型,樹的節點是一個個物件。我們透過存取這些物件就能夠存取XML文件的內容。
下面我們來看一個簡單的例子,看看在DOM中,我們是如何來操作一個XML文件的。這是一個XML文檔,也是我們要操作的物件:
<?xml version="1.0" encoding="UTF-8"?>
<messages>
<message>Good-bye serialization, hello Java!</message>
</messages>
下面,我們需要把這個文件的內容解析到一個個的Java物件中去供程式使用,利用JAXP,我們只需要幾行程式碼就能做到這一點。首先,我們需要建立一個解析器工廠,以利用這個工廠來獲得一個具體的解析器物件:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
我們在這裡使用DocumentBuilderFacotry的目的是為了創建與具體解析器無關的程序,當DocumentBuilderFactory類別的靜態方法newInstance()被呼叫時,它根據一個系統變數來決定具體使用哪一個解析器。又因為所有的解析器都服從於JAXP所定義的接口,所以無論具體使用哪一個解析器,程式碼都是一樣的。所以當在不同的解析器之間進行切換時,只需要更改系統變數的值,而不用更改任何程式碼。這就是工廠所帶來的好處。
DocumentBuilder db = dbf.newDocumentBuilder();
當取得一個工廠物件後,使用它的靜態方法newDocumentBuilder()方法可以取得一個DocumentBuilder對象,這個物件代表了特定的DOM解析器。但具體是哪一種解析器,微軟的或是IBM的,對於程式而言並不重要。
然後,我們就可以利用這個解析器來對XML文件進行解析了:
Document doc = db.parse("c:/xml/message.xml");
DocumentBuilder的parse()方法接受一個XML文件名稱作為輸入參數,並傳回一個Document對象,這個Document物件就代表了一個XML文件的樹模型。以後所有的XML文件的操作,都與解析器無關,直接在這個Document物件上進行操作就可以了。而具體對Document操作的方法,就是由DOM定義的了。
從得到的Document物件開始,我們就可以開始我們的DOM之旅了。使用Document物件的getElementsByTagName()方法,我們可以得到一個NodeList對象,一個Node物件代表了一個XML文件中的一個標籤元素,而NodeList對象,觀其名而知其意,所代表的是一個Node對象的列表:
NodeList nl = doc.getElementsByTagName("message");
我們透過這樣一條語句所得到的是XML文件中所有<message>標籤對應的Node物件的一個列表。然後,我們可以使用NodeList物件的item()方法來得到列表中的每一個Node物件:
Node my_node = nl.item(0);
當一個Node物件被建立之後,保存在XML文件中的資料就被提取出來並封裝在這個Node中了。在這個範例中,要提取Message標籤內的內容,我們通常會使用Node物件的getNodeValue()方法:
String message = my_node.getFirstChild().getNodeValue();
請注意,這裡也使用了一個getFirstChild()方法來取得message下面的第一個子Node物件。雖然在message標籤下面除了文字外並沒有其它子標籤或屬性,但是我們堅持在這裡使用getFirseChild()方法,這主要和W3C對DOM的定義有關。 W3C把標籤內的文字部分也定義成一個Node,所以要先得到代表文字的那個Node,我們才能夠使用getNodeValue()來取得文字的內容。現在,既然我們已經能夠從XML檔案中提取出資料了,我們就可以把這些資料用在適當的地方,來建構應用程式。
DOM實例
先說說這個例子到底要做的是什麼吧,我們在一個名為link.xml檔案中保存了一些URL位址,我們希望可以透過DOM把這些URL讀出並顯示出來,也可以反過來向這個XML檔中寫入加入的URL位址。很簡單,卻很實用,也足夠來例示DOM的絕大部分用法了。
第一個程式我們稱為xmldisplay.Java,主要的功能就是讀取這個XML檔案中各個節點的內容,然後在格式化輸出在System.out上,我們來看看這個程式:
import Javax.xml.parsers .*;
import org.w3c.dom.*;
這是引入必要的類,因為在這裡使用的是Sun所提供的XML解析器,因而需要引入Java.xml.parsers包,其中包含了有DOM解析器和SAX解析器的具體實作。 org.w3c.dom包中定義了w3c所製定的DOM介面。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("links.xml");
doc.normalize();
除了上面講到的,還有一個小技巧,對Document對象調用normalize(),可以去掉XML文檔中作為格式化內容的空白而映射在DOM樹中的不必要的Text Node對象。否則你得到的DOM樹可能不如你想像的那樣。特別是在輸出的時候,這個normalize()比較有用。
NodeList links =doc.getElementsByTagName("link");
剛才說過,XML文件中的空白符號也會被當作物件映射在DOM樹。因而,直接呼叫Node方法的getChildNodes方法有時候會有些問題,有時不能夠傳回所期望的NodeList物件。解決的方法是使用Element的getElementByTagName(String),而回傳的NodeLise就是所期待的物件了。然後,可以用item()方法來擷取想要的元素。
for (int i=0;i<links.getLength();i++){
Element link=(Element) links.item(i);
System.out.print("Content: ");
System.out.println(link.getElementsByTagName("text").item(0).getFirstChild();
.getNodeValue());
……
上面的程式碼片段就完成了XML文件內容的格式化輸出。只要注意到一些細節的問題,例如getFirstChile()方法和getElementsByTagName()方法的使用,這些還是比較容易的。
下面的內容,就是在修改了DOM樹後重新寫入XML文件中去的問題了。這個程式名為xmlwrite.Java。在JAXP1.0版本中,並沒有直接的類別和方法能夠處理XML文件的寫入問題,需要藉助其它套件中的一些輔助類別。而在JAXP1.1版中,引進了對XSLT的支持,所謂XSLT,就是對XML文件進行變換(Translation)後,得到一個新的文檔結構。利用這個新加入的功能,我們就能夠很方便的把新生成或者修改後的DOM樹從新寫回到XML文件中去了,下面我們來看看代碼的實現,這段代碼的主要功能是向links .xml檔中加入一個新的link節點:
import Javax.xml.parsers.*;
import Javax.xml.transform.*;
import Javax.xml.transform.dom.DOMSource;
import Javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
新引入的Java.xml.transform包中的幾個類,就是用來處理XSLT變換的。
我們希望在上面的XML文件中加入一個新的link節點,因而首先還是要讀入links.xml文件,構建一個DOM樹,然後再對這個DOM樹進行修改(添加節點),最後把修改後的DOM寫回links.xml檔案中:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("links.xml");
doc.normalize();
//---取得變數----
String text="Hanzhong's Homepage";
String url=" www.hzliu.com ";
String author="Hzliu Liu";
String discription="A site from Hanzhong Liu, give u lots of suprise!!!";
為了看清重點,簡化程序,我們把要加入的內容硬編碼到記憶String物件中,而實際操作中,往往利用一個介面來提取使用者輸入,或透過JDBC從資料庫中提取想要的內容。
Text textseg;
Element link=doc.createElement("link");
首先應該明了的是,無論什麼類型的Node,Text型的也好,Attr型的也好,Element型的也好,它們的創建都是透過Document對象中的createXXX()方法來建立的(XXX代表特定要建立的類型),因此,我們要在XML文件中新增一個link項目,首先要建立一個link物件:
Element linktext=doc.createElement("text") ;
textseg=doc.createTextNode(text);
linktext.appendChild(textseg);
link.appendChild(linktext);
……
建立節點的過程可能有些千篇一律,但要注意的地方是,對Element中所包含的text(在DOM中,這些text也是代表了一個Node的,因此也必須為它們創建對應的node),不能直接用Element物件的setNodeValue()方法來設定這些text的內容,而需要用創建的Text物件的setNodeValue()方法來設定文本,這樣才能夠把創建的Element和其文本內容加入到DOM樹中。看看前面的程式碼,你會更好的理解這一點:
doc.getDocumentElement().appendChild(link);
最後,不要忘記把創建好的節點加入到DOM樹。 Document類別的getDocumentElement()方法,傳回代表文檔根節點的Element物件。在XML文件中,根節點一定是唯一的。
TransformerFactory tFactory =TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new Java.io.File("links.xml"));
transformer.transform(source, result);
然後就是用XSLT把DOM樹輸出了。這裡的TransformerFactory也同樣應用了工廠模式,使得具體的程式碼與特定的變換器無關。實現的方法和DocumentBuilderFactory相同,這兒就不贅述了。 Transformer類別的transfrom方法接受兩個參數、一個資料來源Source和一個輸出目標Result。這裡分別使用的是DOMSource和StreamResult,這樣就能夠把DOM的內容輸出到一個輸出流中,當這個輸出流是一個檔案的時候,DOM的內容就被寫入到文件中去了。
如何利用JSP開發SAX應用?
SAX是Simple API for XML的縮寫,它並不是W3C官方所提出的標準,可以說是"民間"的事實標準。實際上,它是一種社區性質的討論產物。雖然如此,在XML中對SAX的應用絲毫不比DOM少,幾乎所有的XML解析器都會支援它。
與DOM比較而言,SAX是一種輕量級的方法。我們知道,在處理DOM的時候,我們需要讀入整個的XML文檔,然後在記憶體中建立DOM樹,產生DOM樹上的每個Node對象。當文件比較小的時候,這不會造成什麼問題,但是一旦文件大起來,處理DOM就會變得相當耗時費力。特別是其對於記憶體的需求,也將是成倍的成長,以至於在某些應用程式中使用DOM是一件很不划算的事(例如在applet中)。這時候,一個較好的替代方案就是SAX。
SAX在概念上與DOM完全不同。首先,有別於DOM的文檔驅動,它是事件驅動的,也就是說,它並不需要讀入整個文檔,而文檔的讀入過程也就是SAX的解析過程。所謂事件驅動,是指一種基於回呼(callback)機制的程式運作方法。 (如果你對Java新的代理事件模型比較清楚的話,就會很容易理解這種機制了)在XMLReader接受XML文檔,在讀入XML文檔的過程中就進行解析,也就是說讀入文檔的過程和解析的過程是同時進行的,這和DOM差別很大。解析開始之前,需要向XMLReader註冊一個ContentHandler,也就是相當於一個事件監聽器,在ContentHandler中定義了很多方法,例如startDocument(),它定制了當在解析過程中,遇到文檔開始時應該處理的事情。當XMLReader讀到適當的內容,就會拋出對應的事件,並把這個事件的處理權代理給ContentHandler,呼叫其對應的方法來回應。
這樣泛泛的說來或許有些不容易理解,別急,後面的例子會讓你明白SAX的解析過程。看看這個簡單XML檔:
<POEM>
<AUTHOR>Ogden Nash</AUTHOR>
<TITLE>Fleas</TITLE>
<LINE>Adam</LINE>
</POEM>
當XMLReader讀到<POEM>標籤時,就會呼叫ContentHandler.startElement()方法,並把標籤名POEM當作參數傳遞過去。在你實作的startElement()方法中需要做對應的動作,以處理當<POEM>出現時應該做的事情。各個事件隨著解析的過程(也就是文檔讀入的過程)一個個順序的被拋出,相應的方法也會被順序的調用,最後,當解析完成,方法都被調用後,對文檔的處理也就完成了。下面的這個表,列出了在解析上面的那個XML檔案的時候,順序被呼叫的方法:
遇到的專案方法回調
{文檔開始} startDocument()
<POEM> startElement(null,"POEM",null,{Attributes})
"n" characters("<POEM>n...", 6, 1)
<AUTHOR> startElement(null,"AUTHOR",null,{Attributes})
"Ogden Nash" characters("<POEM>n...", 15, 10)
</AUTHOR> endElement(null,"AUTHOR",null)
"n" characters("<POEM>n...", 34, 1)
<TITLE> startElement(null,"TITLE",null,{Attributes})
"Fleas" characters("<POEM>n...", 42, 5)
</TITLE> endElement(null,"TITLE",null)
"n" characters("<POEM>n...", 55, 1)
<LINE> startElement(null,"LINE",null,{Attributes})
"Adam" characters("<POEM>n...", 62, 4)
</LINE> endElement(null,"LINE",null)
"n" characters("<POEM>n...", 67, 1)
</POEM> endElement(null,"POEM",null)
{文檔結束} endDocument()
ContentHandler實際上是一個接口,當處理特定的XML文件的時候,就需要為其創建一個實現了ContentHandler的類別來處理特定的事件,可以說,這個實際上就是SAX處理XML文件的核心。下面我們來看看定義在其中的一些方法:
void characters(char[] ch, int start, int length):這個方法用來處理在XML檔案中讀到字串,它的參數是一個字元數組,以及讀到的這個字串在這個數組中的起始位置與長度,我們可以很容易的用String類別的建構方法來得到這個字串的String類別:String charEncontered=new String(ch,start,length)。
void startDocument():當遇到文件的開頭的時候,呼叫這個方法,可以在其中做一些預處理的工作。
void endDocument():和上面的方法相對應,當文件結束的時候,呼叫這個方法,可以在其中做一些善後的工作。
void startElement(String namespaceURI, String localName, String qName, Attributes atts):當讀到一個開始標籤的時候,會觸發這個方法。在SAX1.0版本中並不支援名域,而在新的2.0版本中提供了對名域的支持,這兒參數中的namespaceURI就是名域,localName是標籤名,qName是標籤的修飾前綴,當沒有使用名域的時候,這兩個參數都未null。而atts是這個標籤所包含的屬性清單。透過atts,可以得到所有的屬性名稱和對應的值。要注意的是SAX中一個重要的特點就是它的流式處理,在遇到一個標籤的時候,它並不會記錄下以前所碰到的標籤,也就是說,在startElement()方法中,所有你所知道的訊息,就是標籤的名字和屬性,至於標籤的嵌套結構,上層標籤的名字,是否有子元屬等等其它與結構相關的信息,都是不得而知的,都需要你的程序來完成。這使得SAX在程式處理上沒有DOM來得那麼方便。
void endElement(String namespaceURI, String localName, String qName):這個方法和上面的方法相對應,在遇到結束標籤的時候,呼叫這個方法。
我們還是沿用講DOM的時候使用的那個文件例子,但首先,我們先看一個簡單一些的應用,我們希望能夠統計一下XML文件中各個標籤出現的次數。這個例子很簡單,但是足以闡述SAX程式設計的基本想法了。一開始當然還是import語句了:
import org.xml.sax.helpers.DefaultHandler;
import Javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import Java.util.*;
import Java.io.*;
然後,我們建立一個繼承於DefaultHandler的類,具體的程式邏輯在這裡可以暫且放在一邊,要注意的是程式的結構:
public class SAXCounter extends DefaultHandler {
private Hashtable tags; //這個Hashtable用來記錄tag出現的次數
// 處理文件前的工作
public void startDocument() throws SAXException {
tags = new Hashtable();//初始化Hashtable
}
//對每一個開始元屬進行處理
public void startElement(String namespaceURI, String localName,
String rawName, Attributes atts)
throws SAXException
{
String key = localName;
……
我們來看看這段程式做了些什麼。在main()方法中,主要做的就是建立解析器,然後解析文件。實際上,在這裡創建SAXParser物件的時候,為了使程式碼於具體的解析器無關,使用了同DOM中一樣的設計技巧:透過一個SAXParserFactory類別來創建具體的SAXParser對象,這樣,當需要使用不同的解析器的時候,要改變的,只是一個環境變數的值,而程式的程式碼可以保持不變。這就是FactoryMethod模式的思想。在這兒不再具體講了,如果還有不明白的,可以參考上面DOM的解釋,原理是一樣的。
不過在這裡還有一點要注意的地方,就是SAXParser類別和XMLReader類別之間的關係。你可能有些迷糊了吧,實際上SAXParser是JAXP中對XMLReader的一個封裝類,而XMLReader是定義在SAX2.0種的一個用來解析文件的介面。你可以同樣的呼叫SAXParser或XMLReader中的parser()方法來解析文檔,效果是完全一樣的。不過在SAXParser中的parser()方法接受更多的參數,可以對不同的XML文件資料來源進行解析,因而使用起來要比XMLReader方便一些。
這個例子只涉及了SAX的一點皮毛,而下面的這個,可就要高級一些了。下面我們要實現的功能,在DOM的例子中已經有實現了,就是從XML文檔中讀出內容並格式化輸出,雖然程式邏輯看起來還是很簡單,但是SAX可不比DOM哦,看著吧。
前面說過,當遇到一個開始標籤的時候,在startElement()方法中,我們並不能夠得到這個標籤在XML文件中所處的位置。這在處理XML文件的時候是個大麻煩,因為在XML中標籤的語義,有一部分是由其所處的位置所決定的。而且在一些需要驗證文檔結構的程式中,這更是一個問題。當然,沒有解決不了的問題了,我們可以使用一個堆疊來實現文件結構的紀錄。
棧的特徵是先進先出,我們現在的想法是,在startElemnt()方法中用push將這個標籤的名字加入堆疊中,在endElement()方法中在把它pop出來。我們知道對一個結構良好的XML而言,其嵌套結構是完備的,每一個開始標籤總是會對應一個結束標籤,而且不會出現標籤嵌套之間的錯位。因而,每一次startElement()方法的調用,必然會對應一個endElement()方法的調用,這樣push和pop也是成對出現的,我們只需要分析棧的結構,就可以很容易的知道當前標籤所處在文檔結構中的位置了。
public class SAXReader extends DefaultHandler {
Java.util.Stack tags=new Java.util.Stack();
……
在這裡雖然沒有使用到棧的分析,但實際上棧的分析是一件很容易的事情,應為Java.util.Stack繼承了Java.util.Vector類,而且Stack中的元素是按棧的結構由底至上排列的,因個,我們可以使用Vector類別的size()方法來得到Stack的元素個數,還可以使用Vector的get(int)方法來得到具體的每一個元屬。實際上,如果把Stack的元素從底向上逐一排列出來,我們就得到了從XML根節點到當前節點的一條唯一的路徑,有了這條路徑的信息,文檔的結構就在清楚不過了。
到目前為止,我們已經掌握了對於XML程式設計的兩大利器:DOM和SAX,也知道了該如何在一個Java程式中使用它們。 DOM程式設計相對簡單,但是速度比較慢,佔用記憶體多,而S AX程式設計複雜一些,但是速度快,佔用記憶體少。所以,我們應該根據不同的環境來選擇使用不同的方法。大部分的XML應用基本上都可以用它們來解決。需要特別說明的是,DOM和SAX其實都是語言無關的,並非Java所獨有,也就是說,只要有對應的語言實現,DOM和SAX可以應用在任何物件導向的語言中。