HTMLParser使用

from @汀芷,  感謝做者!(轉載他人,本身收藏)html

HTMLParser具備小巧,快速的優勢,缺點是相關文檔比較少(英文的也少),不少功能須要本身摸索。對於初學者仍是要費一些功夫的,而一旦上手之後,會發現HTMLParser的結構設計很巧妙,很是實用,基本你的各類需求均可以知足。
    這裏我根據本身這幾個月來的經驗,寫了一點入門的東西,但願能對新學習HTMLParser的朋友們有所幫助。(不過當年高考本人語文只比及格高一分,因此文法方面的問題還但願你們多多擔待)
    
    HTMLParser的核心模塊是org.htmlparser.Parser類,這個類實際完成了對於HTML頁面的分析工做。這個類有下面幾個構造函數:
    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;
    和一個靜態類public static Parser createParser (String html, String charset);

    對於大多數使用者來講,使用最多的是經過一個URLConnection或者一個保存有網頁內容的字符串來初始化Parser,或者使用靜態函數來生成一個Parser對象。ParserFeedback的代碼很簡單,是針對調試和跟蹤分析過程的,通常不須要改變。而使用Lexer則是一個相對比較高級的話題,放到之後再討論吧。
    這裏比較有趣的一點是,若是須要設置頁面的編碼方式的話,不使用Lexer就只有靜態函數一個方法了。對於大多數中文頁面來講,好像這是應該用得比較多的一個方法。

   下面是初始化Parser的例子。java


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.baizeju.com
*/
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 ) {            
        }
    }
}
加劇的部分測試了幾種不一樣的初始化方法,後面的顯示告終果。你們看到能Parser出內容就能夠了,如何操做訪問Parser的內容咱們在後面討論。node

HTMLParser將解析過的信息保存爲一個樹的結構。Node是信息保存的數據類型基礎。
請看Node的定義:
public interface Node extends Cloneable;

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

實際咱們用HTMLParser最多的是處理HTML頁面,Filter或Visitor相關的函數是必須的,而後第一類和第二類函數是用得最多的。第一類函數比較容易理解,下面用例子說明一下第二類函數。
下面是用於測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head>
<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
    <div id="logoindex">
        <!--這是註釋-->
        白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
    </div>
    白澤居-www.baizeju.com
</div>
</body>
</html>

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

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.baizeju.com
*/
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 );
        }
    }
}

輸出結果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
getPlainText:
toHtml:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
toHtml(true):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
toHtml(false):<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
toString:Doctype Tag : !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/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.baizeju.com
toHtml:<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head>
toHtml(true):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title></head>
toHtml(false):<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</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.baizeju.com
    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.w3.org/1999/xhtml"
getPlainText:


        
                
                白澤居-www.baizeju.com
白澤居-www.baizeju.com
        
        白澤居-www.baizeju.com



toHtml:<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--這是註釋-->
                白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
        </div>
        白澤居-www.baizeju.com
</div>
</body>
</html>
toHtml(true):<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--這是註釋-->
                白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
        </div>
        白澤居-www.baizeju.com
</div>
</body>
</html>
toHtml(false):<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
        <div id="logoindex">
                <!--這是註釋-->
                白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
        </div>
        白澤居-www.baizeju.com
</div>
</body>
</html>
toString:Tag (240[2,0],283[2,43]): html xmlns="http://www.w3.org/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.baizeju.com\n
        Tag (376[8,0],409[8,33]): a href="http://www.baizeju.com"
          Txt (409[8,33],428[8,52]): 白澤居-www.baizeju.com
          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.baizeju.com\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的內容,對應的就是第一行<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">,這個比較好理解。
從這個輸出結果中,也能夠看出內容的樹狀結構。或者說是樹林結構。在Page內容的第一層Tag,如DOCTYPE,head和html,分別造成了一個最高層的Node節點(不少人可能對第二個和第四個Node的內容有點奇怪。實際上這兩個Node就是兩個換行符號。HTMLParser把HTML頁面內容中的全部換行,空格,Tab等都轉換成了相應的Tag,因此就出現了這樣的Node。雖然內容少可是級別高,呵呵)
getPlainTextString是把用戶能夠看到的內容都包含了。有趣的有兩點,一是<head>標籤中的Title內容是在plainText中的,可能在標題中可見的也算可見吧。另外就是象前面說的,HTML內容中的換行符什麼的,也都成了plainText,這個邏輯上好像有點問題。

另外可能你們發現toHtml,toHtml(true)和toHtml(false)的結果沒什麼區別。實際也是這樣的,若是跟蹤HTMLParser的代碼就能夠發現,Node的子類是AbstractNode,其中實現了toHtml()的代碼,直接調用toHtml(false),而AbstractNode的三個子類RemarkNode,TagNode和TextNode中,toHtml(boolean verbatim)的實現中,都沒有處理verbatim參數,因此三個函數的結果是如出一轍的。若是你不須要實現你本身的什麼特殊處理,簡單使用toHtml就能夠了。

HTML的Node類繼承關係以下圖(這個是從別的文章Copy的):正則表達式

 

 

AbstractNodes是Node的直接子類,也是一個抽象類。它的三個直接子類實現是RemarkNode,用於保存註釋。在輸出結果的toString部分中能夠看到有一個"Rem (345[6,2],356[6,13]): 這是註釋",就是一個RemarkNode。TextNode也很簡單,就是用戶可見的文字信息。TagNode是最複雜的,包含了HTML語言中的全部標籤,並且能夠擴展(擴展 HTMLParser 對自定義標籤的處理能力)。TagNode包含兩類,一類是簡單的Tag,實際就是不能包含其餘Tag的標籤,只能作葉子節點。另外一類是CompositeTag,就是能夠包含其餘Tag,是分支節點編程

HTMLParser遍歷了網頁的內容之後,以樹(森林)結構保存告終果。HTMLParser訪問結果內容的方法有兩種。使用Filter和使用Visitor。

(一)Filter
顧名思義,Filter就是對於結果進行過濾,取得須要的內容。HTMLParser在org.htmlparser.filters包以內一共定義了16個不一樣的Filter,也能夠分爲幾類。
判斷類Filter
TagNameFilter
HasAttributeFilter
HasChildFilter
HasParentFilter
HasSiblingFilter
IsEqualFilter
邏輯運算Filter
AndFilter
NotFilter
OrFilter
XorFilter
其餘Filter
NodeClassFilter
StringFilter
LinkStringFilter
LinkRegexFilter
RegexFilter
CssSelectorNodeFilter

全部的Filter類都實現了org.htmlparser.NodeFilter接口。這個接口只有一個主要函數:
boolean accept (Node node);
各個子類分別實現這個函數,用於判斷輸入的Node是否符合這個Filter的過濾條件,若是符合,返回true,不然返回false。

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

下面是用於測試的HTML文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>白澤居-www.baizeju.com</title>< /head>
<html xmlns="http://www.w3.org/1999/xhtml">
<body >
<div id="top_main">
    <div id="logoindex">
        <!--這是註釋-->
        白澤居-www.baizeju.com
<a href="http://www.baizeju.com">白澤居-www.baizeju.com</a>
    </div>
    白澤居-www.baizeju.com
</div>
</body>
</html>
測試代碼:(這裏只列出了Main函數,所有代碼請參考 HTMLParser使用入門(2)- Node內容,本身添加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();
        }
    }
輸出結果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================
能夠看出文件中兩個Div節點都被取出了。下面能夠針對這兩個DIV節點進行操做

2.2 HasChildFilter
下面讓咱們看看HasChildFilter。剛剛看到這個Filter的時候,我想固然地認爲這個Filter返回的是有Child的Tag。直接初始化了一個
NodeFilter filter = new HasChildFilter();
結果調用NodeList nodes = parser.extractAllNodesThatMatch(filter);的時候HasChildFilter內部直接發生NullPointerException。讀了一下HasChildFilter的代碼,才發現,實際HasChildFilter是返回有符合條件的子節點的節點,須要另一個Filter做爲過濾子節點的參數。缺省的構造函數雖然能夠初始化,可是因爲子節點的Filter是null,因此使用的時候發生了Exception。從這點來看,HTMLParser的代碼還有不少能夠優化的的地方。呵呵。

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

注意HasChildFilter還有一個構造函數:
public HasChildFilter (NodeFilter filter, boolean recursive)
若是recursive是false,則只對第一級子節點進行過濾。好比前面的例子,body和top_main都是在第一級的子節點裏就有DIV節點,因此匹配上了。若是咱們用下面的方法調用:
NodeFilter filter = new HasChildFilter( innerFilter, true );
輸出結果:
getText:html xmlns="http://www.w3.org/1999/xhtml"
=================================================
getText:body 
=================================================
getText:div id="top_main"
=================================================
能夠看到輸出結果中多了一個html xmlns="http://www.w3.org/1999/xhtml",這個是整個HTML頁面的節點(根節點),雖然這個節點下直接沒有DIV節點,可是它的子節點body下面有DIV節點,因此它也被匹配上了。

2.3 HasAttributeFilter
HasAttributeFilter有3個構造函數:
public HasAttributeFilter ();
public HasAttributeFilter (String attribute);
public HasAttributeFilter (String attribute, String value);
這個Filter能夠匹配出包含制定名字的屬性,或者制定屬性爲指定值的節點。仍是用例子說明比較容易。

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

什麼也沒有輸出。

調用方法2:
NodeFilter filter = new HasAttributeFilter( "id" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:div id="top_main"
=================================================
getText:div id="logoindex"
=================================================

調用方法3:
NodeFilter filter = new HasAttributeFilter( "id", "logoindex" );
NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:div id="logoindex"
=================================================

很簡單吧。呵呵

2.4 其餘判斷列Filter
HasParentFilter和HasSiblingFilter的功能與HasChildFilter相似,你們本身試一下就應該瞭解了。

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


(三)邏輯運算Filter
前面介紹的都是簡單的Filter,只能針對某種單一類型的條件進行過濾。HTMLParser支持對於簡單類型的Filter進行組合,從而實現複雜的條件。原理和通常編程語言的邏輯運算是同樣的。
3.1 AndFilter

AndFilter能夠把兩種Filter進行組合,只有同時知足條件的Node纔會被過濾。
測試代碼:
NodeFilter filterID = new HasAttributeFilter( "id" );
NodeFilter filterChild = new HasChildFilter(filterA);
NodeFilter filter = new AndFilter(filterID, filterChild);
輸出結果:
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);
輸出結果:
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));
輸出結果:
getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
=================================================
getText:

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

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

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

=================================================
getText:
        
=================================================
getText:
                
=================================================
getText:這是註釋
=================================================
getText:
                白澤居-www.baizeju.com

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

=================================================
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);
輸出結果:
getText:div id="top_main"
=================================================

(四)其餘Filter
4.1 NodeClassFilter
這個Filter用於判斷節點類型是不是某個特定的Node類型。在HTMLParser使用入門(2)- Node內容 中咱們已經瞭解了Node的不一樣類型,這個Filter就能夠針對類型進行過濾。
測試代碼:
           NodeFilter filter = new NodeClassFilter(RemarkNode.class);
            NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:這是註釋
=================================================
能夠看到只有RemarkNode(註釋)被輸出了。

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

測試代碼:
           NodeFilter filter = new StringFilter("www.baizeju.com");
            NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:白澤居-title-www.baizeju.com
=================================================
getText:
                白澤居-字符串1-www.baizeju.com

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

=================================================
能夠看到包含title,兩個內容字符串和連接的文本字符串的Tag都被輸出了,可是註釋和連接Tag自己沒有輸出。

4.3 LinkStringFilter
這個Filter用於判斷連接中是否包含某個特定的字符串,能夠用來過濾出指向某個特定網站的連接。
測試代碼:
           NodeFilter filter = new LinkStringFilter("www.baizeju.com");
            NodeList nodes = parser.extractAllNodesThatMatch(filter);
輸出結果:
getText:a href="http://www.baizeju.com"
=================================================

4.4 其餘幾個Filter
其餘幾個Filter也是根據字符串對不一樣的域進行判斷,與前面這些的區別主要就是支持正則表達式。這個不在本文的討論範圍之內,你們能夠本身實驗一下。數組

HTMLParser遍歷了網頁的內容之後,以樹(森林)結構保存告終果。HTMLParser訪問結果內容的方法有兩種。使用Filter和使用Visitor。
下面介紹使用Visitor訪問內容的方法。

4.1 NodeVisitor
從簡單方面的理解,Filter是根據某種條件過濾取出須要的Node再進行處理。Visitor則是遍歷內容樹的每個節點,對於符合條件的節點進行處理。實際的結果殊途同歸,兩種不一樣的方法能夠達到相同的結果。
下面是一個最多見的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();
        }
    }
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/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

能夠看到,開始遍歷因此的節點之前,beginParsing先被調用,而後處理的是中間的Node,最後在結束遍歷之前,finishParsing被調用。由於我設置的 recurseChildren和recurseSelf都是false,因此Visitor沒有訪問子節點也沒有訪問根節點的內容。中間輸出的兩個\n就是咱們在HTMLParser使用詳解(1)- 初始化Parser 中討論過的最高層的那兩個換行。

咱們先把recurseSelf設置成true,看看會發生什麼。
NodeVisitor visitor = new NodeVisitor( false, true) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/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.w3.org/1999/xhtml"
finishedParsing
能夠看到,HTML頁面的第一層節點都被調用了。

咱們再用下面的方法調用看看:
NodeVisitor visitor = new NodeVisitor( true, false) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/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.baizeju.com
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.baizeju.com 
This is Text:Txt (378[6,34],408[8,0]): \n\t\t白澤居-字符串1-www.baizeju.com\n
This is Text:Txt (441[8,33],465[8,57]): 白澤居-連接文本-www.baizeju.com
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.baizeju.com\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
能夠看到,全部的子節點都出現了,除了剛剛例子裏面的兩個最上層節點This is Tag:head和This is Tag:html xmlns="http://www.w3.org/1999/xhtml"。

想讓它們都出來,只須要
NodeVisitor visitor = new NodeVisitor( true, true) {
輸出結果:
beginParsing
This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/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.baizeju.com
visitEndTag:/title
visitEndTag:/head
This is Text:Txt (244[1,121],246[2,0]): \n
This is Tag:html xmlns="http://www.w3.org/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.baizeju.com 
This is Text:Txt (378[6,34],408[8,0]): \n\t\t白澤居-字符串1-www.baizeju.com\n
This is Tag:a href="http://www.baizeju.com"
This is Text:Txt (441[8,33],465[8,57]): 白澤居-連接文本-www.baizeju.com
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.baizeju.com\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
哈哈,這下調用清楚了,你們在須要處理的地方增長本身的代碼好了。


4.2 
其餘Visitor
HTMLParser還定義了幾個其餘的Visitor。HtmlPage,NodeVisitor,ObjectFindingVisitor,StringFindingVisitor,TagFindingVisitor,TextExtractingVisitor,UrlModifyingVisitor,它們都是NodeVisitor的子類,實現了一些特定的功能。筆者我的的感受是沒什麼用處,若是你須要什麼特定的功能,還不如本身寫一個,想在這些裏面找到適合你須要的,化的時間可能更多。反正你們看看代碼就發現,它們每一個都沒幾行真正有效的代碼。HTMLParser 是一個用來解析 HTML 文檔的開放源碼項目,它具備小巧、快速、使用簡單的特色以及擁有強大的功能。對該項目還不瞭解的朋友能夠參照 2004 年三月份我發表的文章--《從HTML中攫取你所需的信息》,這篇文章介紹如何經過 HTMLParser 來提取 HTML 文檔中的文本數據以及提取出文檔中的全部連接或者是圖片等信息。瀏覽器

如今該項目的最新版本是 Integration Build 1.6,與以前版本的差異在於代碼結構的調整、固然也有一些功能的提高以及BugFix,同時對字符集的處理也更加自動了。比較遺憾的該項目並無詳盡的使用文檔,你只能藉助於它的 API 文檔、一兩個簡單例子以及源碼來熟悉它。app

若是是 HTML 文檔,那麼用 HTMLParser 已經差很少能夠知足你至少 90%的需求。一個 HTML 文檔中可能出現的標籤差很少在 HTMLParser 中都有對應的類,甚至包括一些動態的腳本標籤,例如 <%...%> 這種 JSP 和 ASP 用到的標籤都有相應的JspTag 對應。HTMLParser 的強大功能還體如今你能夠修改每一個標籤的屬性或者它所包含的文本內容並生成新的 HTML 文檔,好比你能夠文檔中的連接地址偷偷的改爲你本身的地址等等。關於 HTMLParser 的強大功能,其實上一篇文章已經介紹不少,這裏再也不累贅,咱們今天要講的是另一個用途--處理自定義標籤。less

首先咱們先解釋一下什麼叫自定義標籤,我把全部不是 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

 

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文檔中出現的標籤訂義已經應有盡有,咱們盡能夠參考這些標籤解析類的源碼來學習如何實現一個標籤的解析類,從而擴展出更豐富多彩的應用程序。

相關文章
相關標籤/搜索