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文档中出现的标签订义已经应有尽有,咱们尽能够参考这些标签解析类的源码来学习如何实现一个标签的解析类,从而扩展出更丰富多彩的应用程序。