![]() |
|
級別: 初級
冬 劉 ( javayou@gmail.com ), 廣州市摩網信息技術有限公司技術副總經理
2006 年 4 月 13 日
本文闡述如何利用 HTMLParser 項目對 HTML 或者 WML 文檔中出現(xiàn)的一些特殊的或者是自定義的標簽進行處理。<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
HTMLParser 是一個用來解析 HTML 文檔的開放源碼項目,它具有小巧、快速、使用簡單的特點以及擁有強大的功能。對該項目還不了解的朋友可以參照 2004 年三月份我發(fā)表的文章--《 從HTML中攫取你所需的信息 》,這篇文章介紹如何通過 HTMLParser 來提取 HTML 文檔中的文本數(shù)據(jù)以及提取出文檔中的所有鏈接或者是圖片等信息。
現(xiàn)在該項目的最新版本是 Integration Build 1.6,與之前版本的差別在于代碼結構的調整、當然也有一些功能的提升以及 BugFix,同時對字符集的處理也更加自動了。比較遺憾的該項目并沒有詳盡的使用文檔,你只能借助于它的 API 文檔、一兩個簡單例子以及源碼來熟悉它。
如果是 HTML 文檔,那么用 HTMLParser 已經差不多可以滿足你至少 90% 的需求。一個 HTML 文檔中可能出現(xiàn)的標簽差不多在 HTMLParser 中都有對應的類,甚至包括一些動態(tài)的腳本標簽,例如 <%...%> 這種 JSP 和 ASP 用到的標簽都有相應的 JspTag 對應。HTMLParser 的強大功能還體現(xiàn)在你可以修改每個標簽的屬性或者它所包含的文本內容并生成新的 HTML 文檔,比如你可以文檔中的鏈接地址偷偷的改成你自己的地址等等。關于 HTMLParser 的強大功能,其實上一篇文章已經介紹很多,這里不再累贅,我們今天要講的是另外一個用途--處理自定義標簽。
首先我們先解釋一下什么叫自定義標簽,我把所有不是 HTML 腳本語言中定義的標簽稱之為自定義標簽,比如可以是 <scriptlet>、<book> 等等,這是我們自己創(chuàng)造出來的標簽。你可能會很奇怪,因為這些標簽一旦用在 HTML 文檔中是沒有任何效果的,那么我們換另外一個例子,假如你要解析的不是 HTML 文檔,而是一個 WML(Wireless Markup Lauguage)文檔呢?WML 文檔中的 card,anchor 等標簽 HTMLParser 是沒有現(xiàn)成的標簽類來處理的。還有就是你同樣可以用 HTMLParser 來處理 XML 文檔,而 XML 文檔中所有的標簽都是你自己定義的。
為了使我們的例子更具有代表意義,接下來我們將給出一段代碼用來解析出 WML 文檔中的所有鏈接,了解 WML 文檔的人都知道,WML 文檔中除了與 HTML 文檔相同的鏈接寫法外,還多了一種標簽叫 <anchor>,例如在一個 WML 文檔我們可以用下面兩種方式來表示一個鏈接。
?
<a >Java自由人</a> 或者: <anchor> Java自由人 <go method="get"> <postfield name="cat_id" value="1"/> </go> </anchor> |
?
(更多的時候使用 anchor 的鏈接用來提交一個表單。) 如果我們還是使用 LinkTag 來遍歷整個 WML 文檔的話,那 Anchor 中的鏈接將會被我們所忽略掉。
下面我們先給出一個簡單的例子,然后再敘述其中的道理。這個例子包含兩個文件,一個是WML 的測試腳本文件 test.wml,另外一個是 Java 程序文件 HyperLinkTrace.java,內容如下:
?
![]() ![]() |
![]()
|
?
<?xml version="1.0"?> <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> <wml> <card title="Java自由人登錄"> <p> 用戶名:<input type="text" name="username" size="15"/> 密碼:<input type="text" name="password" size="15"/> <br/> <anchor>現(xiàn)在登錄 <go href="/wap/user.do" method="get"> <postfield name="name" value="$(username)"/> <postfield name="password" value="$(password)"/> <postfield name="eventSubmit_Login" value="WML"/> </go> </anchor><br/> <a href="/wap/index.vm">返回首頁</a> </p> </card> </wml> |
?
test.wml 中的粗體部分是我們需要提取出來的鏈接。
?
![]() ![]() |
![]()
|
?
package demo.htmlparser; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.net.URL; import org.htmlparser.Node; import org.htmlparser.NodeFilter; import org.htmlparser.Parser; import org.htmlparser.PrototypicalNodeFactory; import org.htmlparser.tags.CompositeTag; import org.htmlparser.tags.LinkTag; import org.htmlparser.util.NodeList; /** * 用來遍歷WML文檔中的所有超鏈接 * @author Winter Lau */ public class HyperLinkTrace { public static void main(String[] args) throws Exception { //初始化HTMLParser Parser parser = new Parser(); parser.setEncoding("8859_1"); parser.setInputHTML(getWmlContent()); //注冊新的結點解析器 PrototypicalNodeFactory factory = new PrototypicalNodeFactory (); factory.registerTag(new WmlGoTag ()); parser.setNodeFactory(factory); //遍歷符合條件的所有節(jié)點 NodeList nlist = parser.extractAllNodesThatMatch(lnkFilter); for(int i=0;i<nlist.size();i++){ CompositeTag node = (CompositeTag)nlist.elementAt(i); if(node instanceof LinkTag){ LinkTag link = (LinkTag)node; System.out.println("LINK: \t" + link.getLink()); } else if(node instanceof WmlGoTag){ WmlGoTag go = (WmlGoTag)node; System.out.println("GO: \t" + go.getLink()); } } } /** * 獲取測試的WML腳本內容 * @return * @throws Exception */ static String getWmlContent() throws Exception{ URL url = ParserTester.class.getResource("/demo/htmlparser/test.wml"); File f = new File(url.toURI()); BufferedReader in = new BufferedReader(new FileReader(f)); StringBuffer wml = new StringBuffer(); do{ String line = in.readLine(); if(line==null) break; if(wml.length()>0) wml.append("\r\n"); wml.append(line); }while(true); return wml.toString(); } /** * 解析出所有的鏈接,包括行為<a>與<go> */ static NodeFilter lnkFilter = new NodeFilter() { public boolean accept(Node node) { if(node instanceof WmlGoTag) return true; if(node instanceof LinkTag) return true; return false; } }; /** * WML文檔的GO標簽解析器 * @author Winter Lau */ static class WmlGoTag extends CompositeTag { private static final String[] mIds = new String[] {"GO"}; private static final String[] mEndTagEnders = new String[] {"ANCHOR"}; public String[] getIds (){ return (mIds); } public String[] getEnders (){ return (mIds); } public String[] getEndTagEnders (){ return (mEndTagEnders); } public String getLink(){ return super.getAttribute("href"); } public String getMethod(){ return super.getAttribute("method"); } } } |
?
上面這段代碼比較長,可以分成下面幾部分來看:
1. getWmlContent方法: 該方法用來獲取在同一個包中的test.wml腳本文件的內容并返回字符串。
2. 靜態(tài)屬性lnkFilter:這是一個NodeFilter的匿名類所構造的實例。該實例用來傳遞給HTMLParser告知需要提取哪些節(jié)點。在這個例子中我們僅需要提取鏈接標簽以及我們自定義的一個GO標簽。
3. 嵌套類WmlGoTag:這也是最為重要的一部分,這個類用來告訴HTMLParser如何去解析<go>這樣一個節(jié)點。我們先看看下面這個HTMLParser的節(jié)點類層次圖:
如上圖所示,HTMLParser將一個文檔分成三種節(jié)點分別是:Remark(注釋);Text(文本);Tag(標簽)。而標簽又分成兩種分別是簡單標簽(Tag)和復合標簽(CompositeTag),像<img><br/>這種標簽稱為簡單標簽,因為標簽不會再包含其它內容。而像<a href="xxxx">Home</a>這種類型的標簽,因為標簽會嵌套文本或者其他標簽的稱為復合標簽,也就是對應著CompositeTag這個類。簡單標簽的實現(xiàn)類很簡單,只需要擴展Tag類并覆蓋getIds方法以返回標簽的識別文本,例如<img>標簽應該返回包含"img"字符串的數(shù)組,具體的代碼可以參考HTMLParser自帶的ImageTag標簽類的實現(xiàn)。
從上圖可清楚看出,復合標簽事實上是對簡單標簽的擴展,HTMLParser在處理一個復合標簽時需要知道該標簽的起始標識以及結束標識,也就是我們在前面給出的源碼中的兩個方法getIds和getEnders,一般來講,標簽出現(xiàn)都是成對的,因此這兩個方法一般返回相同的值。另外一個方法getEndTagEnders,這個方法用來返回父一級的標簽名稱,例如<tr>的父一級標簽應該是<table>。這個方法的必要性在于HTML對格式的要求很不嚴格,在很多的HTML文檔中的一些標簽經常是有開始標識,但是沒有結束標識,由于瀏覽器的超強適應能力使這種情況出現(xiàn)的很頻繁,因此HTMLParser利用這個方法來輔助判斷一個標簽是否已經結束。由于WML文檔的格式要求非常嚴格,因此上例源碼中的getEndTagEnders方法事實上可有可無。
4. 入口方法main:該方法初始化HTMLParser并注冊新的節(jié)點解析器,解析文檔并打印運行結果。
最后我們編譯并運行這個例子,便可以得到下面的運行結果:
?
GO: /wap/user.do LINK: /wap/index.vm |
?
HTMLParser本身就是一個開放源碼的項目,它對于HTML文檔中出現(xiàn)的標簽定義已經應有盡有,我們盡可以參考這些標簽解析類的源碼來學習如何實現(xiàn)一個標簽的解析類,從而擴展出更豐富多彩的應用程序。
-
HTMLParser
http://htmlparser.sourceforge.net/
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
