1.4.1 確定要實現(xiàn)的基本功能
鑒于對瀏覽器開發(fā)難度的充分考慮,以及現(xiàn)有人員的水平,擬定實現(xiàn)以下功能,以及需要考慮但暫不予實現(xiàn)的功能。
需要實現(xiàn)的包括:
(1) 界面:包括窗口,菜單,輸入框,工具條,滾動條等的支持。
(2) 詞法分析:必須實現(xiàn)實用的HTML詞法分析,支持HTML4.0全部元素。
(3) 實現(xiàn)簡單網(wǎng)頁的布局:實現(xiàn)對簡單網(wǎng)頁的查看。
(4) 支持基本IO,支持采用線程的網(wǎng)絡(luò)傳輸。
需要考慮的功能:
(1) JavaScript支持
(2) 漢字支持
(3) 圖片格式支持
(4) 表單支持
(5) 頁面元素的消息響應
1.4.2 人員分工
由于情況的變動,造成了人員比較緊張,在前期準備工作中,人力充沛,使得收集的資料比較完備,打下了較好的基礎(chǔ)。在后期簡化了目標,雖然人員減少,但也能夠?qū)崿F(xiàn)主要的工作?紤]到網(wǎng)絡(luò)是比較獨立的部份,把它分出去由專人負責。 |
|
第二章 HTML詞法分析器的設(shè)計及其應用
HTML詞法分析是瀏覽器設(shè)計的基礎(chǔ)環(huán)節(jié)之一,也是整個設(shè)計過程中重要的前端工作,其數(shù)據(jù)結(jié)構(gòu)的擬定與接下來的語法分析和布局算法密切相關(guān),詞法分析的效率與準確性、容錯性也關(guān)系到整個瀏覽器設(shè)計的質(zhì)量。
下面將介紹一個HTML詞法分析器——Bit Token的設(shè)計思路。
Bit Token是Netbit Browser的HTML詞法分析器,使用標準C編程,Netbit Browser是基于Linux/Gtk的瀏覽器,開放源碼項目,
2.1 Bit Token的組成及其功能
Bit Token作為Netbit Browser的詞法分析部份,負責對接收的HTML代碼進行詞法分析,主要的目的是提取網(wǎng)頁中元素的名稱及其屬性,并以恰當?shù)男问剑窗匆欢ǖ臄?shù)據(jù)結(jié)構(gòu))加以保存,也就是完成了將數(shù)據(jù)流離散化、結(jié)構(gòu)化的過程。
主要由以下幾個部分組成:
1、初始化:完成對數(shù)據(jù)結(jié)構(gòu)的初始化,主要是分配內(nèi)存,變量賦初值。
2、主體的數(shù)據(jù)流分析:逐字符的進行判斷,確定數(shù)據(jù)的歸屬類型。
3、元素的分析:提取元素的名稱、屬性和值域。
4、釋放:主要是對內(nèi)存的釋放。
2.2 數(shù)據(jù)結(jié)構(gòu)
typedef struct BitTokenContext
{char * strBuffer; //當前正在處理的HTML代碼
int bufferLength;
int curPosition;
char * global_strBuffer; //全局HTML代碼
int global_bufferLength;
int global_curPosition;
BitTokenList *tokenList; //元素節(jié)點鏈表
BitTokenList *tokenList_tail;
BitPTagList pTagList; //元素名稱表,指向靜態(tài)數(shù)據(jù)
}BitTokenContext,*BitPTokenContext;
BitTokenContext是用于存儲當前待分析網(wǎng)頁全局屬性的數(shù)據(jù)結(jié)構(gòu),其中TokenList是核心的元素節(jié)點鏈表。詞法分析的目的就是生成這樣一個鏈表。下面給出該鏈表的數(shù)據(jù)結(jié)構(gòu),是很簡單的雙向鏈表。
typedef struct TokenList
{ BitToken *token; //元素節(jié)點
struct TokenList *priou;
struct TokenList *next;
}BitTokenList,*BitPTokenList;
以下是元素節(jié)點的數(shù)據(jù)結(jié)構(gòu):
typedef struct BitToken
{int type; //節(jié)點類型,如定義的HTML_BODY,HTML_TXT等。
char *pData; //如果是HTML_TXT型元素,則為其內(nèi)容,否則為空
BOOL end; //是否是結(jié)束元素,如</body>
BitTokenAttrList *attrList; //元素屬性鏈表,因為可能有多個屬性,所以使用鏈表存儲
BitTokenAttrList *attrList_tail;
}BitToken,*BitPToken;
請注意,以上出現(xiàn)tail標記的指針變量,如BitTokenList * tokenList_tail等,其作用是用于保存鏈表結(jié)尾節(jié)點指針,便于在釋放內(nèi)存時,直接找到鏈尾,提高了算法的效率。
2.3 算法
2.3.1 基本算法:
首先介紹基本的算法:
(1) 從存儲網(wǎng)頁的字符串中,順序讀入一個字符
(2) 如果遇到 < ,認為遇到TAG(元素),處理該元素,使用函數(shù)Token_ConsumTag(),處理完畢后,指針移到該元素尾。
(3) 如果遇到回車、空格,則跳過。
(4) 如果遇到 > ,則跳過(不應該出現(xiàn)此情況,為了容錯)。
(5) 如果非以上情況,則認為遇到文字,處理這段文字,使用函數(shù)Token_Consum_PlainText()。處理完畢,指針指向下一個元素首。
(6) 循環(huán)以上操作,直到該網(wǎng)頁分析完畢。
由此看來,主算法十分簡單而清晰,主要是Token_ConsumTag()和Token_Consum_PlainText()這兩個函數(shù)起關(guān)鍵作用,由于其中涉及到許多細節(jié)問題,此處不予詳述。
2.3.2 算法效率與改進:
采用以上的基本算法,是可用的,但當網(wǎng)頁比較大的時候,比如600K,該算法的效率成倍下降,這主要是由于要處理的字符串太大,在內(nèi)存中完成查找、替換、復制、移動等操作,響應時間明顯下降。對此的改進辦法就是分段進行詞法分析,不僅極大的提高了效率(在某些情況下約提高30倍),也有利于瀏覽器整體設(shè)計,因為當網(wǎng)頁較大時,若等待全部內(nèi)容傳輸完畢,再一次性完成詞法分析和布局,用戶會感到等待時間過長,一般現(xiàn)在成熟的瀏覽器都采用邊傳輸,邊分析,邊顯示。
分段進行詞法分析的算法復雜度明顯增加,比如,當每段定為1024字節(jié),在第1024字節(jié)處,可能正好將一個完整元素截斷,按常規(guī)分析方法會造成錯誤。解決的辦法是,采用回溯,確認要分析的部份至少包含1個完整元素。
具體做法是:判斷1024字節(jié)處是否為元素結(jié)束字符 ‘>’,如果不是,則判斷前一個字節(jié),直到找到元素結(jié)束字符為止,這樣可保證至少包含一個元素。
采用分段進行詞法分析,實際每次分析的代碼會不足1024字節(jié),余下的部份匯入到下一段的分析過程即可,直到所有內(nèi)容被分析完畢。
2.4詞法分析的結(jié)果
下面是一段很簡單的HTML代碼。
<html>
<img src=“go.gif” width=200 height=100>
<a >首都在線</a>
</html>
分析后,數(shù)據(jù)存儲結(jié)構(gòu)如下
:
可以看到,詞法分析的結(jié)果是一個元素節(jié)點鏈表,每個節(jié)點的屬性也形成了一個鏈表,元素節(jié)點是有先后順序的,元素屬性的先后順序是無所謂的。
詞法分析將網(wǎng)頁的文本數(shù)據(jù)流以清晰的結(jié)構(gòu)表現(xiàn)出來,這樣,在后面的應用中就可以很容易的遍歷各節(jié)點,并輕松地獲得各元素節(jié)點的屬性。
2.5 HTML詞法分析的應用
2.5.1 應用舉例:
HTML詞法分析程序通常應用于瀏覽器設(shè)計、網(wǎng)頁制作軟件設(shè)計等領(lǐng)域,本人以一個使用VC開發(fā)的軟件“HTML智能分析”來舉例說明,下載網(wǎng)址:
http://netbit_browser.myetang.com/introduce.html。
“HTML智能分析”同樣使用Bit Token詞法分析器,“HTML智能分析”是一個網(wǎng)頁信息提取、處理軟件。
具有以下主要功能:
1、智能提取網(wǎng)頁中的文字信息,智能排版,并可在進行編輯后保存。
2、統(tǒng)計網(wǎng)頁的有關(guān)信息。
3、根據(jù)用戶設(shè)置的版式,將分析和編輯的結(jié)果,自動生成新的網(wǎng)頁。
用戶可使用該軟件來將HTML轉(zhuǎn)為TXT格式,其對HTML中文字內(nèi)容的提取準確、快速、不含冗余信息,版式工整清晰,保持本來面貌。
其主要設(shè)計思路是,在Bit Token詞法分析器的基礎(chǔ)上,結(jié)合瀏覽器布局的基本算法,對影響到TXT版面效果的元素進行處理。
比如<PRE>標記,代表所包含的內(nèi)容瀏覽器應不予分析,按TXT格式輸出,而如表格
等元素則意味著需要換行。而在HTML中,在無<PRE>這種特殊情況時,回車都是忽略不記的。這就造成了矛盾。使用常規(guī)的簡單算法進行HTML到TXT的轉(zhuǎn)換無法解決這些問題。造成轉(zhuǎn)換后的版式“失真“。而“HTML智能分析”卻能很好的解決。
由于“HTML智能分析”使用了底層的詞法分析技術(shù),還可以很容易的過濾掉<SCRIPT>與<STYLE>(樣式表)。并可以對網(wǎng)頁中的元素進行統(tǒng)計和語法校驗。
以下是該程序的片斷:
pTtokenList=global_cx->tokenList; //取首節(jié)點
while(pTtokenList!=NULL) //循環(huán)直至處理完所有節(jié)點
{
switch(pTtokenList->token->type)
{//根據(jù)節(jié)點類型,做不同的處理
case HTML_TITLE: ……
case HTML_TEXT: ……
default: ……
} //switch
pTtokenList=pTtokenList->next; //取下一個節(jié)點
} //while
這段程序?qū)嶋H上就是一個簡單的語法分析和布局的過程。
2.5.2 Bit Token在應用中存在的問題及修改意見
由于HTML的標記多是成對出現(xiàn)的,并且存在<SCRIPT>這樣的特殊元素,其內(nèi)容為Javascript程序,函數(shù)的字符串參數(shù)等可能包含其它的元素標記。例如語句:Alert(“<font> is a tag”);
因此,在詞法分析時要對<SCRIPT>標記進行特殊處理,遇到<SCRIPT>就應逐字符讀入后面的內(nèi)容,直到遇到下一個</SCRIPT>標記。目前的Bit Token由于開發(fā)時間所限,未對其加以特殊處理,存在一些問題,但由于瀏覽器對Javascript的支持是較復雜的工作,目前的Netbit Browser尚不予實現(xiàn),因而沒有導致明顯問題,而“HTML智能分析”這個軟件只是需要對Javascript進行刪除操作,也不會造成影響。盡管如此,對<SCRIPT>的特殊處理還是有待完善,盡管這同時也會帶來一些問題,需要進行大量的測試,來保證新加入代碼的穩(wěn)定性。
正如前面所述,HTML詞法分析是瀏覽器設(shè)計的基礎(chǔ)環(huán)節(jié)之一,但并非最重要和最具難度的環(huán)節(jié),若想開發(fā)出效果較好的瀏覽器產(chǎn)品,還要在布局和GUI設(shè)計上多下功夫。
第三章 瀏覽器JavaScript支持的實現(xiàn)
本部份主要針對Mozilla和Netscape瀏覽器源代碼的JavaScript部份進行了分析,闡述了瀏覽器Javascript實現(xiàn)的機制。
3.1基本的JavaScript 開發(fā)環(huán)境
JavaScript Reference與JavaScript API:
JavaScript Reference是Mozilla所使用JavaScript開發(fā)環(huán)境,是使用ANSI C的獨立的開發(fā)包,據(jù)Mozilla文檔介紹,該開發(fā)包涉及到超過160家公司的版權(quán)。而且被廣泛使用,實際已成為了進行JavaScript應用開發(fā)的標準平臺。
JavaScript Reference可以用于建立包含JavaScript runtime的Library或 DLL。既可以編譯成小的 "shell" 程序(像早期的BASIC),又連接Library后生成交互式的JavaScript解釋器,也可以用來解釋.js 文件。由于使用了ANSI C編程,可以用VC、GCC等編譯器在不同平臺下編譯。
生成的"shell" 程序,對比瀏覽器對JavaScript的支持,相同之處是使用相同的包含JavaScript runtime的Library 或 DLL,我們把這部份相同的Library或 DLL稱為JavaScript API,我們實際開發(fā)JavaScript應用,也是在JavaScript API基礎(chǔ)上工作,而不用過多考慮其內(nèi)部的實現(xiàn)。關(guān)于JavaScript API,參見JavaScript API詳解。JavaScript API實際就是Javscript解釋器的對外接口函數(shù)庫。
3.2 JavaScript Engine
JavaScript Engine是瀏覽器開發(fā)者為了利用JavaScript API來實現(xiàn)實際應用而設(shè)立的中間層,用于初始化JavaScript環(huán)境,提供對JavaScript解釋、執(zhí)行的接口。瀏覽器主體程序的設(shè)計者可以通過JavaScript Engine,方便的實現(xiàn)各種應用,畢竟JavaScript API太基礎(chǔ)了,直接使用不太方便。
下面介紹JavaScript Engine的主要功能和實現(xiàn)方法。這也包含了利用JavaScript API進行應用的基本思路。
(1) 初始化:
內(nèi)存分配:rt=JS_Init(10000L);
初始化cx:cx = JS_NewContext(rt, STACK_CHUNK_SIZE);
初始化globalObj:globalObj = JS_NewObject(cx, &globalClass, 0, 0);
定義標準類:JS_InitStandardClasses(cx, globalObj);
定義系統(tǒng)函數(shù):JS_DefineFunctions(cx, globalObj, g_functions);
定義報錯函數(shù):JS_SetErrorReporter(cx,JS_ErrorReporter);
注冊其它類:
RegisterClassPoint (cx,globalObj);
RegisterClassSize (cx,globalObj);
RegisterClassRect (cx,globalObj);
RegisterClassPolygon (cx,globalObj);
RegisterClassColorKey (cx,globalObj);
RegisterClassTDTimer (cx,globalObj);
初始化定時器:TDTimerListInit();
(2) 提供對JavaScript解釋、執(zhí)行的接口函數(shù):
TD_EvaluateScript(JSContext *cx,JSObject *obj, const char *bytes, uintN length,const char *filename, uintN lineno,jsval *rval)
3.3 JavaScript與瀏覽器接合
基本概念:JavaScript操作HTML元素的常見方式
例:
<html><head>
<script><!--
function ChangeImage(index)
{image0.src="a"+index+".gif";}
--></script>
</head>
<a onmouseover="ChangeImage(0);">軍人</a><br>
<a onmouseover="ChangeImage(1);">眼睛</a><br>
<img id="image0" src="a0.gif"></img>
</html>
當鼠標移到文字上時,觸發(fā)事件mouseover,調(diào)用ChangeImage()函數(shù),使得圖像源(SRC)發(fā)生變化,重新調(diào)入新圖片。
由此產(chǎn)生兩個關(guān)鍵問題:
1. javascript如何獲取HTML元素的名稱和屬性。
2. javascript如何改變HTML元素的屬性,并操作WIDGET重畫。
下面分別闡述這兩個問題:
首先介紹涉及到的瀏覽器流程:
問題1解決:HTML元素作為Javascript對象進行注冊。
注冊過程在BuildModel中進行。BuildModel的首要任務(wù)是將Token后的結(jié)點按包含關(guān)系展成一棵樹。其次就是要將某些結(jié)點注冊為JavaScript對象。
注冊的過程是:
定義新對象:JSObject *proto;
初始化該對象:
TD_JSXMLElementClassInit(JS_GetGlobalContext(),
(void **)&proto))
使用JS_DefineObject或JS_NewObject定義對象屬性:
根據(jù)是否定義了該元素的名稱區(qū)別對待:
if(TD_XMLContentIsNamedItem(aElement,&aName))
{ parent = js_GetGlobalObject();
*aReturn=JS_DefineObject(JS_GetGlobalContext(),js_GetGlobalObject(),aName->mStr,&ElementClass,proto,JSPROP_ENUMERATE);
}
else
{ parent=aElement->parent->mScriptObject;
*aReturn = JS_NewObject(JS_GetGlobalContext(), &ElementClass, proto, parent);
}
將對象加入:
JS_SetPrivate(JS_GetGlobalContext(), (JSObject *)*aReturn, aElement);這樣,在編譯時,HTML元素的標識就能被Javascript編譯器識別,否則會報錯變量未定義。
問題2解決:利用注冊給對象的函數(shù)實現(xiàn)操作符的功能化。
具體可理解為:當image0.src=”1.gif”被執(zhí)行時,相當于為對象設(shè)置或改變屬性,此時SetElementProperty函數(shù)被調(diào)用(該函數(shù)在注冊該對象時由JSXMLElementClassInit捆綁給該對象,其內(nèi)容由用戶自己定義),SetElementProperty通過函數(shù)指針調(diào)用函數(shù)TD_JSXMLSetAtrByID,改變結(jié)點樹上結(jié)點屬性,并重新生成該節(jié)點對應的widget,重畫界面。
問題3:如何建立Javascript對象與結(jié)點樹上結(jié)點的對應?
解決: Javascript對象與結(jié)點樹是同時生成的,它們的共同性質(zhì)是結(jié)點具有相同屬性,Javascript對象根據(jù)ID屬性查找樹,找到要操作的對應結(jié)點。
3.4 瀏覽器消息響應
在主消息循環(huán)中調(diào)用TDWidgetProcessMsg,處理與widget有關(guān)消息。
首先:取得當前焦點所在的widget
pWidget=TDWidgetGetAtPoint(pThis->baseDoc.base.mWidget,pt,&index);
處理該widget對該消息的響應。
最后一般為調(diào)用JavaScript執(zhí)行,實現(xiàn)實際響應。
TDVOID TDWidgetDoAction(TDPWidgetAction pAnchor)
{
jsval jval;
if(pAnchor)
TD_EvaluateScript(JS_GetGlobalContext(),js_GetGlobalObject(),pAnchor->mAction.mStr,pAnchor->mAction.mLength,TDNULL,0,&jval);
}
其中pAnchor->mAction.mStr即為界面對象(widget)對應的JavaScript源碼,解釋執(zhí)行的結(jié)果就是調(diào)用為該對象注冊的函數(shù)來重畫該widget,從而實現(xiàn)動態(tài)效果。