一、概述
Web服務器的核心是對HTML文件中的各標記(Tag)作出正確的分析,一種編程語言的解釋程序也是對源文件中的保留字進行分析再做解釋的。實際應用中,我們也常常會遇到需要對某一特定類型文件進行關鍵字分析的情況,比如,需要將某個HTML文件下載并同時下載與之相關的.gif、.class等文件,此時就要求對HTML文件中的標記進行分離,找出所需的文件名及目錄。在Java出現以前,類似工作需要對文件中的每個字符進行分析,從中找出所需部分,不僅編程量大,且易出錯。筆者在近期的項目中利用Java的輸入流類StreamTokenizer進行HTML文件的分析,效果較好。在此,我們要實現從已知的Web頁面下載HTML文件,對其進行分析后,下載該頁面中包含的HTML文件(如果在Frame中)、圖像文件和Class(Java Applet)文件。
二、StreamTokenizer類
StreamTokenizer即令牌化輸入流的作用是將一個輸入流中變成令牌流。令牌流中的令牌實體有三類:單詞(即多字符令牌)、單字符令牌和空白(包括Java和C/C++中的說明語句)。
StreamTokenizer類的構造器為: StreamTokenizer(InputStream in)
該類有一些公有實例變量:ttype、sval和nval ,分別表示令牌類型、當前字符串值和當前數字值。當我們需要取得令牌(即HTML中的標記)之間的字符時,應訪問變量sval。而讀向下一個令牌的方法是調用nextToken()。方法nextToken()的返回值是int型,共有四種可能的返回:
StreamTokenizer.TT_NUMBER: 表示讀到的令牌是數字,數字的值是double型,可以從實例變量nval中讀取。
StreamTokenizer.TT_WORD: 表示讀到的令牌是非數字的單詞(其他字符也在其中),單詞可以從實例變量sval中讀取。
StreamTokenizer.TT_EOL: 表示讀到的令牌是行結束符。
如果已讀到流的盡頭,則nextToken()返回TT_EOF。
開始調用nextToken()之前,要設置輸入流的語法表,以便使分析器辨識不同的字符。WhitespaceChars(int low, int hi)方法定義沒有意義的字符的范圍。WordChars(int low, int hi)方法定義構造單詞的字符范圍。
三、程序實現
1、HtmlTokenizer類的實現
對某個令牌流進行分析之前,首先應對該令牌流的語法表進行設置,在本例中,即是讓程序分出哪個單詞是HTML的標記。下面給出針對我們需要的HTML標記的令牌流類定義,它是StreamTokenizer的子類:
?1
???????? import?java.io.*;?
?2
import?java.lang.String;?
?3
class?HtmlTokenizer?extends?
?4
StreamTokenizer?
{?
?5
//定義各標記,這里的標記僅是本例中必須的,?
?6
可根據需要自行擴充?
?7
?static?int?HTML_TEXT=-1;?
?8
?static?int?HTML_UNKNOWN=-2;?
?9
?static?int?HTML_EOF=-3;?
10
?static?int?HTML_IMAGE=-4;?
11
?static?int?HTML_FRAME=-5;?
12
?static?int?HTML_BACKGROUND=-6;?
13
?static?int?HTML_APPLET=-7;?
14
?
15
boolean?outsideTag=true;?//判斷是否在標記之中?
16
?
17
?//構造器,定義該令牌流的語法表。?
18
?public?HtmlTokenizer(BufferedReader?r)?
{?
19
super(r);?
20
this.resetSyntax();?//重置語法表?
21
this.wordChars(0,255);?//令牌范圍為全部字符?
22
this.ordinaryChar('<?');?//HTML標記兩邊的分割符?
23
this.ordinaryChar('>');?
24
?}?//end?of?constructor?
25
?
26
?public?int?nextHtml()
{?
27
int?token;?//令牌?
28
try
{?
29
switch(token=this.nextToken())
{?
30
case?StreamTokenizer.TT_EOF:?
31
//如果已讀到流的盡頭,則返回TT_EOF?
32
return?HTML_EOF;?
33
case?'<?':?//進入標記字段?
34
outsideTag=false;?
35
return?nextHtml();?
36
case?'>':?//出標記字段?
37
outsideTag=true;?
38
return?nextHtml();?
39
case?StreamTokenizer.TT_WORD:?
40
//若當前令牌為單詞,判斷是哪個標記?
41
if?(allWhite(sval))?
42
?return?nextHtml();?//過濾其中空格?
43
else?if(sval.toUpperCase().indexOf("FRAME")?
44
!=-1?&&?!outsideTag)?//標記FRAME?
45
?return?HTML_FRAME;?
46
else?if(sval.toUpperCase().indexOf("IMG")?
47
!=-1?&&?!outsideTag)?//標記IMG?
48
?return?HTML_IMAGE;?
49
else?if(sval.toUpperCase().indexOf("BACKGROUND")?
50
!=-1?&&?!outsideTag)?//標記BACKGROUND?
51
?return?HTML_BACKGROUND;?
52
else?if(sval.toUpperCase().indexOf("APPLET")?
53
!=-1?&&?!outsideTag)?//標記APPLET?
54
?return?HTML_APPLET;?
55
default:?
56
System.out.println?("Unknown?tag:?"+token);?
57
return?HTML_UNKNOWN;?
58
?}?//end?of?case?
59
}catch(IOException?e)
{?
60
System.out.println("Error:"+e.getMessage());}?
61
return?HTML_UNKNOWN;?
62
?}?//end?of?nextHtml?
63
?
64
protected?boolean?allWhite(String?s)
{//過濾所有空格?
65
//實現略?
66
?}//?end?of?allWhite?
67
?
68
}?//end?of?class?
69

?2

?3

?4



?5

?6

?7

?8

?9

10

11

12

13

14

15

16

17

18



19

20

21

22

23

24

25

26



27

28



29



30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59



60

61

62

63

64



65

66

67

68

69
