小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Java解析HTML之HTMLParser使用與詳解

 windli筆記 2012-08-06

HTMLParser具有小巧,快速的優(yōu)點,缺點是相關(guān)文檔比較少(英文的也少),很多功能需要自己摸索。對于初學(xué)者還是要費一些功夫的,而一旦上手以后,會發(fā)現(xiàn)HTMLParser的結(jié)構(gòu)設(shè)計很巧妙,非常實用,基本你的各種需求都可以滿足。
   
這里我根據(jù)自己這幾個月來的經(jīng)驗,寫了一點入門的東西,希望能對新學(xué)習(xí)HTMLParser的朋友們有所幫助。(不過當(dāng)年高考本人語文只比及格高一分,所以文法方面的問題還希望大家多多擔(dān)待)
    
   
HTMLParser的核心模塊是org.htmlparser.Parser類,這個類實際完成了對于HTML頁面的分析工作。這個類有下面幾個構(gòu)造函數(shù):
    public Parser ();
    public Parser (Lexer lexer, ParserFeedback fb);
   public Parser (URLConnection connection, ParserFeedback fb) throws ParserException;
    public Parser (String resource, ParserFeedback feedback) throws ParserException;
  
public Parser (String resource) throws ParserException;
    public Parser (Lexer lexer);
   
public Parser (URLConnection connection) throws ParserException;
   
和一個靜態(tài)類 public static Parser createParser (String html, String charset);

   
對于大多數(shù)使用者來說,使用最多的是通過一個URLConnection或者一個保存有網(wǎng)頁內(nèi)容的字符串來初始化Parser,或者使用靜態(tài)函數(shù)來生成一個Parser對象。ParserFeedback的代碼很簡單,是針對調(diào)試和跟蹤分析過程的,一般不需要改變。而使用Lexer則是一個相對比較高級的話題,放到以后再討論吧。
   
這里比較有趣的一點是,如果需要設(shè)置頁面的編碼方式的話,不使用Lexer就只有靜態(tài)函數(shù)一個方法了。對于大多數(shù)中文頁面來說,好像這是應(yīng)該用得比較多的一個方法。

  
下面是初始化Parser的例子。


package com.baizeju.htmlparsertester;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;

import org.htmlparser.visitors.TextExtractingVisitor;

import org.htmlparser.Parser;

/**
* @author www.
*/
public class Main {
    private static String
ENCODE = "GBK";
    private static void message( String szMsg ) {
        try{System.out.println(new String(szMsg.getBytes(ENCODE), System.getProperty("file.encoding"))); } catch(Exception e ){}
    }
    public static String openFile( String szFileName ) {
        try {
            BufferedReader bis = new BufferedReader(new InputStreamReader(new FileInputStream( new File(szFileName)),
ENCODE) );
            String szContent="";
            String szTemp;
            
            while ( (szTemp = bis.readLine()) != null) {
                szContent+=szTemp+"\n";
            }
            bis.close();
            return szContent;
        }
        catch( Exception e ) {
            return "";
        }
    }
    
   public static void main(String[] args) {
        
        String szContent = openFile( "E:/My Sites/HTMLParserTester.html");
        
        try{
            //Parser parser = Parser.createParser(szContent, ENCODE);
            //Parser parser = new Parser( szContent );
          
 Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
        
           
TextExtractingVisitor visitor = new TextExtractingVisitor();
            parser.
visitAllNodesWith(visitor);
            String
textInPage = visitor.getExtractedText();

            message(textInPage);
        }
        catch( Exception e ) {            
        }
    }
}
加重的部分測試了幾種不同的初始化方法,后面的顯示了結(jié)果。大家看到能Parser出內(nèi)容就可以了,如何操作訪問Parser的內(nèi)容我們在后面討論。

HTMLParser將解析過的信息保存為一個樹的結(jié)構(gòu)。Node是信息保存的數(shù)據(jù)類型基礎(chǔ)。
請看Node的定義:
public interface Node extends Cloneable;

Node中包含的方法有幾類:
對于樹型結(jié)構(gòu)進(jìn)行遍歷的函數(shù),這些函數(shù)最容易理解:
Node
getParent ()取得父節(jié)點
NodeList
getChildren ()取得子節(jié)點的列表
Node
getFirstChild ()取得第一個子節(jié)點
Node
getLastChild ()取得最后一個子節(jié)點
Node
getPreviousSibling ()取得前一個兄弟(不好意思,英文是兄弟姐妹,直譯太麻煩而且不符合習(xí)慣,對不起女同胞了)
Node
getNextSibling ()取得下一個兄弟節(jié)點
取得Node內(nèi)容的函數(shù)
String
getText ()取得文本
String
toPlainTextString()取得純文本信息。
String
toHtml () 取得HTML信息(原始HTML
String
toHtml (boolean verbatim)取得HTML信息(原始HTML
String
toString ()取得字符串信息(原始HTML
Page
getPage ()取得這個Node對應(yīng)的Page對象
int
getStartPosition ()取得這個NodeHTML頁面中的起始位置
int
getEndPosition ()取得這個NodeHTML頁面中的結(jié)束位置
用于Filter過濾的函數(shù):
void
collectInto (NodeList list, NodeFilter filter)基于filter的條件對于這個節(jié)點進(jìn)行過濾,符合條件的節(jié)點放到list中。
用于Visitor遍歷的函數(shù):
void
accept (NodeVisitor visitor)對這個Node應(yīng)用visitor
用于修改內(nèi)容的函數(shù),這類用得比較少
void
setPage (Page page)設(shè)置這個Node對應(yīng)的Page對象
void
setText (String text)設(shè)置文本
void
setChildren (NodeList children)設(shè)置子節(jié)點列表
其他函數(shù)
void
doSemanticAction ()執(zhí)行這個Node對應(yīng)的操作(只有少數(shù)Tag有對應(yīng)的操作)
Object
clone ()接口Clone的抽象函數(shù)。

實際我們用HTMLParser最多的是處理HTML頁面,FilterVisitor相關(guān)的函數(shù)是必須的,然后第一類和第二類函數(shù)是用得最多的。第一類函數(shù)比較容易理解,下面用例子說明一下第二類函數(shù)。
下面是用于測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-www.</title></head>
<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
    <div id="logoindex">
        <!--
這是注釋-->
       
白澤居-www.
<a href="http://www.">
白澤居-www.</a>
    </div>
   
白澤居-www.
</div>
</body>
</html>

測試代碼:
/**
* @author www.
*/

package com.baizeju.htmlparsertester;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;

import org.htmlparser.Node;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.Parser;

/**
* @author www.
*/
public class Main {
    private static String ENCODE = "
GBK";
    private static void message( String szMsg ) {
        try{ System.out.println(new String(szMsg.getBytes(ENCODE), System.getProperty("file.encoding"))); }     catch(Exception e ){}
    }
    public static String
openFile( String szFileName ) {
        try {
            BufferedReader bis = new BufferedReader(new InputStreamReader(new FileInputStream( new File(szFileName)),    ENCODE) );
            String szContent="";
            String szTemp;
            
            while ( (szTemp = bis.readLine()) != null) {
                szContent+=szTemp+"\n";
            }
            bis.close();
            return szContent;
        }
        catch( Exception e ) {
            return "";
        }
    }
    
   public static void main(String[] args) {
        
        try{
            Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
        
            for (NodeIterator i = parser.elements (); i.hasMoreNodes(); ) {
                Node node = i.nextNode();
                message("getText:"+node.getText());
                message("getPlainText:"+node.toPlainTextString());
                message("toHtml:"+node.toHtml());
                message("toHtml(true):"+node.toHtml(true));
                message("toHtml(false):"+node.toHtml(false));
                message("toString:"+node.toString());
                message("=================================================");
            }            
        }
        catch( Exception e ) {     
            System.out.println( "Exception:"+e );
        }
    }
}

輸出結(jié)果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
getPlainText:
toHtml:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
toHtml(true):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
toHtml(false):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
toString:Doctype Tag : !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd; begins at : 0; ends at : 121
=================================================
getText:

getPlainText:

toHtml:

toHtml(true):

toHtml(false):

toString:Txt (121[0,121],123[1,0]): \n
=================================================
getText:head
getPlainText:
白澤居-www.
toHtml:<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-www.</title></head>
toHtml(true):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-www.</title></head>
toHtml(false):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-www.</title></head>
toString:HEAD: Tag (123[1,0],129[1,6]): head
Tag (129[1,6],197[1,74]): meta http-equiv="Content-Type" content="text/html; ...
Tag (197[1,74],204[1,81]): title
    Txt (204[1,81],223[1,100]):
白澤居-www.
    End (223[1,100],231[1,108]): /title
End (231[1,108],238[1,115]): /head

=================================================
getText:

getPlainText:

toHtml:

toHtml(true):

toHtml(false):

toString:Txt (238[1,115],240[2,0]): \n
=================================================
getText:html xmlns="http://www./1999/xhtml"
getPlainText:


        
                
               
白澤居-www.
白澤居-www.
        
       
白澤居-www.



toHtml:<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--
這是注釋-->
               
白澤居-www.
<a href="http://www.">
白澤居-www.</a>
        </div>
       
白澤居-www.
</div>
</body>
</html>
toHtml(true):<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--
這是注釋-->
               
白澤居-www.
<a href="http://www.">
白澤居-www.</a>
        </div>
       
白澤居-www.
</div>
</body>
</html>
toHtml(false):<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--
這是注釋-->
               
白澤居-www.
<a href="http://www.">
白澤居-www.</a>
        </div>
       
白澤居-www.
</div>
</body>
</html>
toString:Tag (240[2,0],283[2,43]): html xmlns="http://www./1999/xhtml"
Txt (283[2,43],285[3,0]): \n
Tag (285[3,0],292[3,7]): body 
    Txt (292[3,7],294[4,0]): \n
    Tag (294[4,0],313[4,19]): div id="top_main"
      Txt (313[4,19],316[5,1]): \n\t
      Tag (316[5,1],336[5,21]): div id="logoindex"
        Txt (336[5,21],340[6,2]): \n\t\t
        Rem (340[6,2],351[6,13]):
這是注釋
        Txt (351[6,13],376[8,0]): \n\t\t
白澤居-www.\n
        Tag (376[8,0],409[8,33]): a href="http://www."
          Txt (409[8,33],428[8,52]):
白澤居-www.
          End (428[8,52],432[8,56]): /a
        Txt (432[8,56],435[9,1]): \n\t
        End (435[9,1],441[9,7]): /div
      Txt (441[9,7],465[11,0]): \n\t
白澤居-www.\n
      End (465[11,0],471[11,6]): /div
    Txt (471[11,6],473[12,0]): \n
    End (473[12,0],480[12,7]): /body
Txt (480[12,7],482[13,0]): \n
End (482[13,0],489[13,7]): /html

=================================================


對于第一個Node的內(nèi)容,對應(yīng)的就是第一行<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">,這個比較好理解。
從這個輸出結(jié)果中,也可以看出內(nèi)容的樹狀結(jié)構(gòu)。或者說是樹林結(jié)構(gòu)。在Page內(nèi)容的第一層Tag,如DOCTYPEheadhtml,分別形成了一個最高層的Node節(jié)點(很多人可能對第二個和第四個Node的內(nèi)容有點奇怪。實際上這兩個Node就是兩個換行符號。HTMLParserHTML頁面內(nèi)容中的所有換行,空格,Tab等都轉(zhuǎn)換成了相應(yīng)的Tag,所以就出現(xiàn)了這樣的Node。雖然內(nèi)容少但是級別高,呵呵)
getPlainTextString
是把用戶可以看到的內(nèi)容都包含了。有趣的有兩點,一是<head>標(biāo)簽中的Title內(nèi)容是在plainText中的,可能在標(biāo)題中可見的也算可見吧。另外就是象前面說的,HTML內(nèi)容中的換行符什么的,也都成了plainText,這個邏輯上好像有點問題。

另外可能大家發(fā)現(xiàn)toHtml,toHtml(true)toHtml(false)的結(jié)果沒什么區(qū)別。實際也是這樣的,如果跟蹤HTMLParser的代碼就可以發(fā)現(xiàn),Node的子類是AbstractNode,其中實現(xiàn)了toHtml()的代碼,直接調(diào)用toHtml(false),而AbstractNode的三個子類RemarkNode,TagNodeTextNode中,toHtml(boolean verbatim)的實現(xiàn)中,都沒有處理verbatim參數(shù),所以三個函數(shù)的結(jié)果是一模一樣的。如果你不需要實現(xiàn)你自己的什么特殊處理,簡單使用toHtml就可以了。

HTML
Node類繼承關(guān)系如下圖(這個是從別的文章Copy的):

 



 AbstractNodes
Node的直接子類,也是一個抽象類。它的三個直接子類實現(xiàn)是RemarkNode,用于保存注釋。在輸出結(jié)果的toString部分中可以看到有一個"Rem (345[6,2],356[6,13]): 這是注釋",就是一個RemarkNode。TextNode也很簡單,就是用戶可見的文字信息。TagNode是最復(fù)雜的,包含了HTML語言中的所有標(biāo)簽,而且可以擴展(擴展 HTMLParser 對自定義標(biāo)簽的處理能力)。TagNode包含兩類,一類是簡單的Tag,實際就是不能包含其他Tag的標(biāo)簽,只能做葉子節(jié)點。另一類是CompositeTag,就是可以包含其他Tag,是分支節(jié)點

HTMLParser遍歷了網(wǎng)頁的內(nèi)容以后,以樹(森林)結(jié)構(gòu)保存了結(jié)果。HTMLParser訪問結(jié)果內(nèi)容的方法有兩種。使用Filter和使用Visitor

(一)Filter
顧名思義,Filter就是對于結(jié)果進(jìn)行過濾,取得需要的內(nèi)容。HTMLParserorg.htmlparser.filters包之內(nèi)一共定義了16個不同的Filter,也可以分為幾類。
判斷類Filter
TagNameFilter
HasAttributeFilter
HasChildFilter
HasParentFilter
HasSiblingFilter
IsEqualFilter
邏輯運算Filter
AndFilter
NotFilter
OrFilter
XorFilter
其他Filter
NodeClassFilter
StringFilter
LinkStringFilter
LinkRegexFilter
RegexFilter
CssSelectorNodeFilter

所有的Filter類都實現(xiàn)了org.htmlparser.NodeFilter接口。這個接口只有一個主要函數(shù):
boolean accept (Node node);
各個子類分別實現(xiàn)這個函數(shù),用于判斷輸入的Node是否符合這個Filter的過濾條件,如果符合,返回true,否則返回false

(二)判斷類Filter
2.1 TagNameFilter
TabNameFilter
是最容易理解的一個Filter,根據(jù)Tag的名字進(jìn)行過濾。

下面是用于測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-www.</title>< /head>
<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
    <div id="logoindex">
        <!--
這是注釋-->
       
白澤居-www.
<a href="http://www.">
白澤居-www.</a>
    </div>
   
白澤居-www.
</div>
</body>
</html>
測試代碼:(這里只列出了Main函數(shù),全部代碼請參考 HTMLParser使用入門(2- Node內(nèi)容,自己添加import部分)
public static void main(String[] args) {
        
        try{
            Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
        
            //
這里是控制測試的部分,后面的例子修改的就是這個地方。
           
 NodeFilter filter = new TagNameFilter ("DIV");
            NodeList nodes = parser.extractAllNodesThatMatch(filter); 

            
            if(nodes!=null) {
                for (int i = 0; i < nodes.size(); i++) {
                    Node textnode = (Node) nodes.elementAt(i);
                    
                    message("getText:"+textnode.getText());
                    message("=================================================");
                }
            }            
        }
        catch( Exception e ) {     
            e.printStackTrace();
        }
    }
輸出結(jié)果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================
可以看出文件中兩個Div節(jié)點都被取出了。下面可以針對這兩個DIV節(jié)點進(jìn)行操作

2.2 HasChildFilter
下面讓我們看看HasChildFilter。剛剛看到這個Filter的時候,我想當(dāng)然地認(rèn)為這個Filter返回的是有ChildTag。直接初始化了一個
NodeFilter filter = new HasChildFilter();
結(jié)果調(diào)用NodeList nodes = parser.extractAllNodesThatMatch(filter);的時候HasChildFilter內(nèi)部直接發(fā)生NullPointerException。讀了一下HasChildFilter的代碼,才發(fā)現(xiàn),實際HasChildFilter是返回有符合條件的子節(jié)點的節(jié)點,需要另外一個Filter作為過濾子節(jié)點的參數(shù)。缺省的構(gòu)造函數(shù)雖然可以初始化,但是由于子節(jié)點的Filternull,所以使用的時候發(fā)生了Exception。從這點來看,HTMLParser的代碼還有很多可以優(yōu)化的的地方。呵呵。

修改代碼:
NodeFilter innerFilter = new TagNameFilter ("DIV");
NodeFilter filter = new HasChildFilter(innerFilter);
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結(jié)果:
getText:body 
=================================================
getText:div id="top_main"
=================================================
可以看到,輸出的是兩個有DIVTagTag節(jié)點。(body有子節(jié)點DIV "top_main","top_main"有子節(jié)點"logoindex"

注意HasChildFilter還有一個構(gòu)造函數(shù):
public HasChildFilter (NodeFilter filter, boolean recursive)
如果recursivefalse,則只對第一級子節(jié)點進(jìn)行過濾。比如前面的例子,bodytop_main都是在第一級的子節(jié)點里就有DIV節(jié)點,所以匹配上了。如果我們用下面的方法調(diào)用:
NodeFilter filter = new HasChildFilter( innerFilter, true );

輸出結(jié)果:
getText:html xmlns="http://www./1999/xhtml"
=================================================
getText:body 
=================================================
getText:div id="top_main"
=================================================
可以看到輸出結(jié)果中多了一個html xmlns="http://www./1999/xhtml",這個是整個HTML頁面的節(jié)點(根節(jié)點),雖然這個節(jié)點下直接沒有DIV節(jié)點,但是它的子節(jié)點body下面有DIV節(jié)點,所以它也被匹配上了。

2.3 HasAttributeFilter
HasAttributeFilter3個構(gòu)造函數(shù):
public HasAttributeFilter ();
public HasAttributeFilter (String attribute);
public HasAttributeFilter (String attribute, String value);
這個Filter可以匹配出包含制定名字的屬性,或者制定屬性為指定值的節(jié)點。還是用例子說明比較容易。

調(diào)用方法1:
NodeFilter filter = new HasAttributeFilter();
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結(jié)果:

什么也沒有輸出。

調(diào)用方法2:
NodeFilter filter = new HasAttributeFilter( "id" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);

輸出結(jié)果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================

調(diào)用方法3:
NodeFilter filter = new HasAttributeFilter( "id", "logoindex" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);

輸出結(jié)果:
getText:div id="logoindex"
=================================================

很簡單吧。呵呵

2.4
其他判斷列Filter
HasParentFilter
HasSiblingFilter的功能與HasChildFilter類似,大家自己試一下就應(yīng)該了解了。

IsEqualFilter
的構(gòu)造函數(shù)參數(shù)是一個Node
public IsEqualFilter (Node node) {
    mNode = node;
}
accept
函數(shù)也很簡單:
public boolean accept (Node node)    {
    return (mNode == node);
}
不需要過多說明了。


(三)邏輯運算Filter
前面介紹的都是簡單的Filter,只能針對某種單一類型的條件進(jìn)行過濾。HTMLParser支持對于簡單類型的Filter進(jìn)行組合,從而實現(xiàn)復(fù)雜的條件。原理和一般編程語言的邏輯運算是一樣的。
3.1 AndFilter

AndFilter
可以把兩種Filter進(jìn)行組合,只有同時滿足條件的Node才會被過濾。
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new AndFilter(filterID, filterChild);
輸出結(jié)果:
getText:div id="logoindex"
=================================================

3.2 OrFilter
把前面的AndFilter換成OrFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new OrFilter(filterID, filterChild);

輸出結(jié)果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================

3.3 NotFilter
把前面的AndFilter換成NotFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new NotFilter(new OrFilter(filterID, filterChild));
輸出結(jié)果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
=================================================
getText:

=================================================
getText:head
=================================================
getText:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
=================================================
getText:title
=================================================
getText:
白澤居-www.
=================================================
getText:/title
=================================================
getText:/head
=================================================
getText:

=================================================
getText:html xmlns="http://www./1999/xhtml"
=================================================
getText:

=================================================
getText:body 
=================================================
getText:

=================================================
getText:
        
=================================================
getText:
                
=================================================
getText:
這是注釋
=================================================
getText:
               
白澤居-www.

=================================================
getText:a href="http://www."
=================================================
getText:
白澤居-www.
=================================================
getText:/a
=================================================
getText:
        
=================================================
getText:/div
=================================================
getText:
       
白澤居-www.

=================================================
getText:/div
=================================================
getText:

=================================================
getText:/body
=================================================
getText:

=================================================
getText:/html
=================================================
getText:

=================================================

除了前面3.2中輸出的幾個Tag,其余的Tag都在這里了。


3.4 XorFilter
把前面的AndFilter換成NotFilter
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new XorFilter(filterID, filterChild);

輸出結(jié)果:
getText:div id="top_main"
=================================================

(四)其他Filter
4.1 NodeClassFilter
這個Filter用于判斷節(jié)點類型是否是某個特定的Node類型。在HTMLParser使用入門(2- Node內(nèi) 中我們已經(jīng)了解了Node的不同類型,這個Filter就可以針對類型進(jìn)行過濾。
測試代碼:
          
NodeFilter filter = new NodeClassFilter(RemarkNode.class);
            NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結(jié)果:
getText:
這是注釋
=================================================
可以看到只有RemarkNode(注釋)被輸出了。

4.2 StringFilter
這個Filter用于過濾顯示字符串中包含制定內(nèi)容的Tag。注意是可顯示的字符串,不可顯示的字符串中的內(nèi)容(例如注釋,鏈接等等)不會被顯示。
修改一下例子代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>
白澤居-title-www.</title></head>
<html xmlns="http://www./1999/xhtml">
<body >
<div id="top_main">
    <div id="logoindex">
        <!--
這是注釋 白澤居-www. -->
       
白澤居-字符串1-www.
<a href="http://www.">
白澤居-鏈接文本-www.</a>
    </div>
   
白澤居-字符串2-www.
</div>
</body>
</html>

測試代碼:
      
     NodeFilter filter = new StringFilter("www.");
            NodeList nodes = parser.extractAllNodesThatMatch(filter);

輸出結(jié)果:
getText:
白澤居-title-www.
=================================================
getText:
               
白澤居-字符串1-www.

=================================================
getText:
白澤居-鏈接文本-www.
=================================================
getText:
       
白澤居-字符串2-www.

=================================================
可以看到包含title,兩個內(nèi)容字符串和鏈接的文本字符串的Tag都被輸出了,但是注釋和鏈接Tag本身沒有輸出。

4.3 LinkStringFilter
這個Filter用于判斷鏈接中是否包含某個特定的字符串,可以用來過濾出指向某個特定網(wǎng)站的鏈接。
測試代碼:
      
     NodeFilter filter = new LinkStringFilter("www.");
            NodeList nodes = parser.extractAllNodesThatMatch(filter);

輸出結(jié)果:
getText:a href="http://www."
=================================================

4.4
其他幾個Filter
其他幾個Filter也是根據(jù)字符串對不同的域進(jìn)行判斷,與前面這些的區(qū)別主要就是支持正則表達(dá)式。這個不在本文的討論范圍以內(nèi),大家可以自己實驗一下。

HTMLParser遍歷了網(wǎng)頁的內(nèi)容以后,以樹(森林)結(jié)構(gòu)保存了結(jié)果。HTMLParser訪問結(jié)果內(nèi)容的方法有兩種。使用Filter和使用Visitor。
下面介紹使用Visitor訪問內(nèi)容的方法。

4.1 NodeVisitor
從簡單方面的理解,Filter是根據(jù)某種條件過濾取出需要的Node再進(jìn)行處理。Visitor則是遍歷內(nèi)容樹的每一個節(jié)點,對于符合條件的節(jié)點進(jìn)行處理。實際的結(jié)果異曲同工,兩種不同的方法可以達(dá)到相同的結(jié)果。
下面是一個最常見的NodeVisitro的例子。
測試代碼:
    public static void main(String[] args) {
        try{
            Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );

            NodeVisitor visitor = new NodeVisitor( false, false ) {
                public void visitTag(Tag tag) {
                   message("This is Tag:"+tag.getText());
                }
                public void visitStringNode (Text string)    {
                     message("This is Text:"+string);
                }
                public void visitRemarkNode (Remark remark) {
                     message("This is Remark:"+remark.getText());
                }
                public void beginParsing () {
                    message("beginParsing");
                }
                public void visitEndTag (Tag tag){
                    message("visitEndTag:"+tag.getText());
                }
                public void finishedParsing () {
                    message("finishedParsing");
                }
            };

            parser.visitAllNodesWith(visitor);
        }
        catch( Exception e ) {     
            e.printStackTrace();
        }
    }
輸出結(jié)果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Text:Txt (244[1,121],246[2,0]): \n
finishedParsing

可以看到,開始遍歷所以的節(jié)點以前,beginParsing先被調(diào)用,然后處理的是中間的Node,最后在結(jié)束遍歷以前,finishParsing被調(diào)用。因為我設(shè)置的 recurseChildrenrecurseSelf都是false,所以Visitor沒有訪問子節(jié)點也沒有訪問根節(jié)點的內(nèi)容。中間輸出的兩個\n就是我們在HTMLParser使用詳解(1- 初始化Parser 中討論過的最高層的那兩個換行。

我們先把recurseSelf設(shè)置成true,看看會發(fā)生什么。
NodeVisitor visitor = new NodeVisitor( false, true) {
輸出結(jié)果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Tag:html xmlns="http://www./1999/xhtml"
finishedParsing
可以看到,HTML頁面的第一層節(jié)點都被調(diào)用了。

我們再用下面的方法調(diào)用看看:
NodeVisitor visitor = new NodeVisitor( true, false) {
輸出結(jié)果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
This is Text:Txt (204[1,81],229[1,106]):
白澤居-title-www.
visitEndTag:/title
visitEndTag:/head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Text:Txt (289[2,43],291[3,0]): \n
This is Text:Txt (298[3,7],300[4,0]): \n
This is Text:Txt (319[4,19],322[5,1]): \n\t
This is Text:Txt (342[5,21],346[6,2]): \n\t\t
This is Remark:
這是注釋 白澤居-www. 
This is Text:Txt (378[6,34],408[8,0]): \n\t\t
白澤居-字符串1-www.\n
This is Text:Txt (441[8,33],465[8,57]):
白澤居-鏈接文本-www.
visitEndTag:/a
This is Text:Txt (469[8,61],472[9,1]): \n\t
visitEndTag:/div
This is Text:Txt (478[9,7],507[11,0]): \n\t
白澤居-字符串2-www.\n
visitEndTag:/div
This is Text:Txt (513[11,6],515[12,0]): \n
visitEndTag:/body
This is Text:Txt (522[12,7],524[13,0]): \n
visitEndTag:/html
finishedParsing
可以看到,所有的子節(jié)點都出現(xiàn)了,除了剛剛例子里面的兩個最上層節(jié)點This is Tag:headThis is Tag:html xmlns="http://www./1999/xhtml"。

想讓它們都出來,只需要
NodeVisitor visitor = new NodeVisitor( true, true) {
輸出結(jié)果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"
This is Text:Txt (121[0,121],123[1,0]): \n
This is Tag:head
This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312"
This is Tag:title
This is Text:Txt (204[1,81],229[1,106]):
白澤居-title-www.
visitEndTag:/title
visitEndTag:/head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Tag:html xmlns="http://www./1999/xhtml"
This is Text:Txt (289[2,43],291[3,0]): \n
This is Tag:body 
This is Text:Txt (298[3,7],300[4,0]): \n
This is Tag:div id="top_main"
This is Text:Txt (319[4,19],322[5,1]): \n\t
This is Tag:div id="logoindex"
This is Text:Txt (342[5,21],346[6,2]): \n\t\t
This is Remark:
這是注釋 白澤居-www. 
This is Text:Txt (378[6,34],408[8,0]): \n\t\t
白澤居-字符串1-www.\n
This is Tag:a href="http://www."
This is Text:Txt (441[8,33],465[8,57]):
白澤居-鏈接文本-www.
visitEndTag:/a
This is Text:Txt (469[8,61],472[9,1]): \n\t
visitEndTag:/div
This is Text:Txt (478[9,7],507[11,0]): \n\t
白澤居-字符串2-www.\n
visitEndTag:/div
This is Text:Txt (513[11,6],515[12,0]): \n
visitEndTag:/body
This is Text:Txt (522[12,7],524[13,0]): \n
visitEndTag:/html
finishedParsing
哈哈,這下調(diào)用清楚了,大家在需要處理的地方增加自己的代碼好了。


4.2
其他Visitor
HTMLParser
還定義了幾個其他的Visitor。HtmlPage,NodeVisitor,ObjectFindingVisitorStringFindingVisitor,TagFindingVisitor,TextExtractingVisitor,UrlModifyingVisitor,它們都是NodeVisitor的子類,實現(xiàn)了一些特定的功能。筆者個人的感覺是沒什么用處,如果你需要什么特定的功能,還不如自己寫一個,想在這些里面找到適合你需要的,化的時間可能更多。反正大家看看代碼就發(fā)現(xiàn),它們每個都沒幾行真正有效的代碼。HTMLParser 是一個用來解析 HTML 文檔的開放源碼項目,它具有小巧、快速、使用簡單的特點以及擁有強大的功能。對該項目還不了解的朋友可以參照 2004 年三月份我發(fā)表的文章--HTML中攫取你所需的信息》,這篇文章介紹如何通過 HTMLParser 來提取 HTML 文檔中的文本數(shù)據(jù)以及提取出文檔中的所有鏈接或者是圖片等信息。

現(xiàn)在該項目的最新版本是 Integration Build 1.6,與之前版本的差別在于代碼結(jié)構(gòu)的調(diào)整、當(dāng)然也有一些功能的提升以及 BugFix,同時對字符集的處理也更加自動了。比較遺憾的該項目并沒有詳盡的使用文檔,你只能借助于它的 API 文檔、一兩個簡單例子以及源碼來熟悉它。

如果是 HTML 文檔,那么用 HTMLParser 已經(jīng)差不多可以滿足你至少 90 的需求。一個 HTML 文檔中可能出現(xiàn)的標(biāo)簽差不多在 HTMLParser 中都有對應(yīng)的類,甚至包括一些動態(tài)的腳本標(biāo)簽,例如 <%...%> 這種 JSP ASP 用到的標(biāo)簽都有相應(yīng)的 JspTag 對應(yīng)。HTMLParser 的強大功能還體現(xiàn)在你可以修改每個標(biāo)簽的屬性或者它所包含的文本內(nèi)容并生成新的 HTML 文檔,比如你可以文檔中的鏈接地址偷偷的改成你自己的地址等等。關(guān)于 HTMLParser 的強大功能,其實上一篇文章已經(jīng)介紹很多,這里不再累贅,我們今天要講的是另外一個用途--處理自定義標(biāo)簽。

首先我們先解釋一下什么叫自定義標(biāo)簽,我把所有不是 HTML 腳本語言中定義的標(biāo)簽稱之為自定義標(biāo)簽,比如可以是 <scriptlet><book> 等等,這是我們自己創(chuàng)造出來的標(biāo)簽。你可能會很奇怪,因為這些標(biāo)簽一旦用在 HTML 文檔中是沒有任何效果的,那么我們換另外一個例子,假如你要解析的不是 HTML 文檔,而是一個 WMLWireless Markup Lauguage)文檔呢?WML 文檔中的 cardanchor 等標(biāo)簽 HTMLParser 是沒有現(xiàn)成的標(biāo)簽類來處理的。還有就是你同樣可以用 HTMLParser 來處理 XML 文檔,而 XML 文檔中所有的標(biāo)簽都是你自己定義的。

為了使我們的例子更具有代表意義,接下來我們將給出一段代碼用來解析出 WML 文檔中的所有鏈接,了解 WML 文檔的人都知道,WML 文檔中除了與 HTML 文檔相同的鏈接寫法外,還多了一種標(biāo)簽叫 <anchor>,例如在一個 WML 文檔我們可以用下面兩種方式來表示一個鏈接。

 

<a href="http://www.?cat_id=1">Java自由人</a>

或者:

<anchor>

Java自由人

    <go href="http://www." method="get">

        <postfield name="cat_id" value="1"/>

</go>

</anchor>

(更多的時候使用 anchor 的鏈接用來提交一個表單。) 如果我們還是使用 LinkTag 來遍歷整個 WML 文檔的話,那 Anchor 中的鏈接將會被我們所忽略掉。

下面我們先給出一個簡單的例子,然后再敘述其中的道理。這個例子包含兩個文件,一個是WML 的測試腳本文件 test.wml,另外一個是 Java 程序文件 HyperLinkTrace.java,內(nèi)容如下:

 


 

1. test.wml

 

<?xml version="1.0"?>

<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"

"http://www./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 中的粗體部分是我們需要提取出來的鏈接。

 


 

2. 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());

 

  //注冊新的結(jié)點解析器

  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腳本內(nèi)容

  * @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標(biāo)簽解析器

  * @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腳本文件的內(nèi)容并返回字符串。

2. 靜態(tài)屬性lnkFilter:這是一個NodeFilter的匿名類所構(gòu)造的實例。該實例用來傳遞給HTMLParser告知需要提取哪些節(jié)點。在這個例子中我們僅需要提取鏈接標(biāo)簽以及我們自定義的一個GO標(biāo)簽。

3. 嵌套類WmlGoTag:這也是最為重要的一部分,這個類用來告訴HTMLParser如何去解析<go>這樣一個節(jié)點。我們先看看下面這個HTMLParser的節(jié)點類層次圖:


如上圖所示,HTMLParser將一個文檔分成三種節(jié)點分別是:Remark(注釋);Text(文本);Tag(標(biāo)簽)。而標(biāo)簽又分成兩種分別是簡單標(biāo)簽(Tag)和復(fù)合標(biāo)簽(CompositeTag),像<img><br/>這種標(biāo)簽稱為簡單標(biāo)簽,因為標(biāo)簽不會再包含其它內(nèi)容。而像<a href="xxxx">Home</a>這種類型的標(biāo)簽,因為標(biāo)簽會嵌套文本或者其他標(biāo)簽的稱為復(fù)合標(biāo)簽,也就是對應(yīng)著CompositeTag這個類。簡單標(biāo)簽的實現(xiàn)類很簡單,只需要擴展Tag類并覆蓋getIds方法以返回標(biāo)簽的識別文本,例如<img>標(biāo)簽應(yīng)該返回包含"img"字符串的數(shù)組,具體的代碼可以參考HTMLParser自帶的ImageTag標(biāo)簽類的實現(xiàn)。

從上圖可清楚看出,復(fù)合標(biāo)簽事實上是對簡單標(biāo)簽的擴展,HTMLParser在處理一個復(fù)合標(biāo)簽時需要知道該標(biāo)簽的起始標(biāo)識以及結(jié)束標(biāo)識,也就是我們在前面給出的源碼中的兩個方法getIdsgetEnders,一般來講,標(biāo)簽出現(xiàn)都是成對的,因此這兩個方法一般返回相同的值。另外一個方法getEndTagEnders,這個方法用來返回父一級的標(biāo)簽名稱,例如<tr>的父一級標(biāo)簽應(yīng)該是<table>。這個方法的必要性在于HTML對格式的要求很不嚴(yán)格,在很多的HTML文檔中的一些標(biāo)簽經(jīng)常是有開始標(biāo)識,但是沒有結(jié)束標(biāo)識,由于瀏覽器的超強適應(yīng)能力使這種情況出現(xiàn)的很頻繁,因此HTMLParser利用這個方法來輔助判斷一個標(biāo)簽是否已經(jīng)結(jié)束。由于WML文檔的格式要求非常嚴(yán)格,因此上例源碼中的getEndTagEnders方法事實上可有可無。

4. 入口方法main:該方法初始化HTMLParser并注冊新的節(jié)點解析器,解析文檔并打印運行結(jié)果。

最后我們編譯并運行這個例子,便可以得到下面的運行結(jié)果:

 

GO:  /wap/user.do

LINK:  /wap/index.vm

HTMLParser本身就是一個開放源碼的項目,它對于HTML文檔中出現(xiàn)的標(biāo)簽定義已經(jīng)應(yīng)有盡有,我們盡可以參考這些標(biāo)簽解析類的源碼來學(xué)習(xí)如何實現(xiàn)一個標(biāo)簽的解析類,從而擴展出更豐富多彩的應(yīng)用程序。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多