HTMLParser 是一個用來解析 HTML 文檔的開放源碼項目,它具備小巧、快速、使用簡單的特色以及擁有強大的功能。對該項目還不瞭解的朋友能夠參照 2004 年三月份我發表的文章--《
從HTML中攫取你所需的信息》,這篇文章介紹如何經過 HTMLParser 來提取 HTML 文檔中的文本數據以及提取出文檔中的全部連接或者是圖片等信息。
如今該項目的最新版本是 Integration Build 1.6,與以前版本的差異在於代碼結構的調整、固然也有一些功能的提高以及 BugFix,同時對字符集的處理也更加自動了。比較遺憾的該項目並無詳盡的使用文檔,你只能藉助於它的 API 文檔、一兩個簡單例子以及源碼來熟悉它。
若是是 HTML 文檔,那麼用 HTMLParser 已經差很少能夠知足你至少 90% 的需求。一個 HTML 文檔中可能出現的標籤差很少在 HTMLParser 中都有對應的類,甚至包括一些動態的腳本標籤,例如 <%...%> 這種 JSP 和 ASP 用到的標籤都有相應的 JspTag 對應。HTMLParser 的強大功能還體如今你能夠修改每一個標籤的屬性或者它所包含的文本內容並生成新的 HTML 文檔,好比你能夠文檔中的連接地址偷偷的改爲你本身的地址等等。關於 HTMLParser 的強大功能,其實上一篇文章已經介紹不少,這裏再也不累贅,咱們今天要講的是另一個用途--處理自定義標籤。
首先咱們先解釋一下什麼叫自定義標籤,我把全部不是 HTML 腳本語言中定義的標籤稱之爲自定義標籤,好比能夠是 <scriptlet>、<book> 等等,這是咱們本身創造出來的標籤。你可能會很奇怪,由於這些標籤一旦用在 HTML 文檔中是沒有任何效果的,那麼咱們換另一個例子,假如你要解析的不是 HTML 文檔,而是一個 WML(Wireless Markup Lauguage)文檔呢?WML 文檔中的 card,anchor 等標籤 HTMLParser 是沒有現成的標籤類來處理的。還有就是你一樣能夠用 HTMLParser 來處理 XML 文檔,而 XML 文檔中全部的標籤都是你本身定義的。
爲了使咱們的例子更具備表明意義,接下來咱們將給出一段代碼用來解析出 WML 文檔中的全部連接,瞭解 WML 文檔的人都知道,WML 文檔中除了與 HTML 文檔相同的連接寫法外,還多了一種標籤叫 <anchor>,例如在一個 WML 文檔咱們能夠用下面兩種方式來表示一個連接。
<a href="http://www.javayou.com?cat_id=1">Java自由人</a> 或者: <anchor> Java自由人 <go href="http://www.javayou.com" method="get"> <postfield name="cat_id" value="1"/> </go> </anchor> |
(更多的時候使用 anchor 的連接用來提交一個表單。) 若是咱們仍是使用 LinkTag 來遍歷整個 WML 文檔的話,那 Anchor 中的連接將會被咱們所忽略掉。
下面咱們先給出一個簡單的例子,而後再敘述其中的道理。這個例子包含兩個文件,一個是WML 的測試腳本文件 test.wml,另一個是 Java 程序文件 HyperLinkTrace.java,內容以下:
1. test.wml
<?
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
>如今登陸
<
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 中的粗體部分是咱們須要提取出來的連接。
2. HyperLinkTrace.java
//HyperLinkTrace.java
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);
//遍歷符合條件的全部節點
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. 靜態屬性lnkFilter:這是一個NodeFilter的匿名類所構造的實例。該實例用來傳遞給HTMLParser告知須要提取哪些節點。在這個例子中咱們僅須要提取連接標籤以及咱們自定義的一個GO標籤。
3. 嵌套類WmlGoTag:這也是最爲重要的一部分,這個類用來告訴HTMLParser如何去解析<go>這樣一個節點。咱們先看看下面這個HTMLParser的節點類層次圖:
如上圖所示,HTMLParser將一個文檔分紅三種節點分別是:Remark(註釋);Text(文本);Tag(標籤)。而標籤又分紅兩種分別 是簡單標籤(Tag)和複合標籤(CompositeTag),像<img><br/>這種標籤稱爲簡單標籤,由於標籤不會再包 含其它內容。而像<a href="xxxx">Home</a>這種類型的標籤,由於標籤會嵌套文本或者其餘標籤的稱爲複合標籤,也就是對應着 CompositeTag這個類。簡單標籤的實現類很簡單,只須要擴展Tag類並覆蓋getIds方法以返回標籤的識別文本,例如<img> 標籤應該返回包含"img"字符串的數組,具體的代碼能夠參考HTMLParser自帶的ImageTag標籤類的實現。
從上圖可清楚看出,複合標籤事實上是對簡單標籤的擴展,HTMLParser在處理一個複合標籤時須要知道該標籤的起始標識以及結束標識,也就是我 們在前面給出的源碼中的兩個方法getIds和getEnders,通常來說,標籤出現都是成對的,所以這兩個方法通常返回相同的值。另一個方法 getEndTagEnders,這個方法用來返回父一級的標籤名稱,例如<tr>的父一級標籤應該是<table>。這個方法 的必要性在於HTML對格式的要求很不嚴格,在不少的HTML文檔中的一些標籤常常是有開始標識,可是沒有結束標識,因爲瀏覽器的超強適應能力使這種狀況 出現的很頻繁,所以HTMLParser利用這個方法來輔助判斷一個標籤是否已經結束。因爲WML文檔的格式要求很是嚴格,所以上例源碼中的 getEndTagEnders方法事實上無關緊要。
4. 入口方法main:該方法初始化HTMLParser並註冊新的節點解析器,解析文檔並打印運行結果。
最後咱們編譯並運行這個例子,即可以獲得下面的運行結果:
GO: /wap/user.do LINK: /wap/index.vm |
HTMLParser自己就是一個開放源碼的項目,它對於HTML文檔中出現的標籤訂義已經應有盡有,咱們盡能夠參考這些標籤解析類的源碼來學習如何實現一個標籤的解析類,從而擴展出更豐富多彩的應用程序。