1.4.1 确定要实现的基本功能
鉴于对浏览器开发难度的充分考虑,以及现有人员的水平,拟定实现以下功能,以及需要考虑但暂不予实现的功能。
需要实现的包括:
(1) 界面:包括窗口,菜单,输入框,工具条,滚动条等的支持。
(2) 词法分析:必须实现实用的HTML词法分析,支持HTML4.0全部元素。
(3) 实现简单网页的布局:实现对简单网页的查看。
(4) 支持基本IO,支持采用线程的网络传输。
需要考虑的功能:
(1) JavaScript支持
(2) 汉字支持
(3) 图片格式支持
(4) 表单支持
(5) 页面元素的消息响应
1.4.2 人员分工
由于情况的变动,造成了人员比较紧张,在前期准备工作中,人力充沛,使得收集的资料比较完备,打下了较好的基础。在后期简化了目标,虽然人员减少,但也能够实现主要的工作。考虑到网络是比较独立的部份,把它分出去由专人负责。 |
|
第二章 HTML词法分析器的设计及其应用
HTML词法分析是浏览器设计的基础环节之一,也是整个设计过程中重要的前端工作,其数据结构的拟定与接下来的语法分析和布局算法密切相关,词法分析的效率与准确性、容错性也关系到整个浏览器设计的质量。
下面将介绍一个HTML词法分析器——Bit Token的设计思路。
Bit Token是Netbit Browser的HTML词法分析器,使用标准C编程,Netbit Browser是基于Linux/Gtk的浏览器,开放源码项目,
2.1 Bit Token的组成及其功能
Bit Token作为Netbit Browser的词法分析部份,负责对接收的HTML代码进行词法分析,主要的目的是提取网页中元素的名称及其属性,并以恰当的形式(即按一定的数据结构)加以保存,也就是完成了将数据流离散化、结构化的过程。
主要由以下几个部分组成:
1、初始化:完成对数据结构的初始化,主要是分配内存,变量赋初值。
2、主体的数据流分析:逐字符的进行判断,确定数据的归属类型。
3、元素的分析:提取元素的名称、属性和值域。
4、释放:主要是对内存的释放。
2.2 数据结构
typedef struct BitTokenContext
{char * strBuffer; //当前正在处理的HTML代码
int bufferLength;
int curPosition;
char * global_strBuffer; //全局HTML代码
int global_bufferLength;
int global_curPosition;
BitTokenList *tokenList; //元素节点链表
BitTokenList *tokenList_tail;
BitPTagList pTagList; //元素名称表,指向静态数据
}BitTokenContext,*BitPTokenContext;
BitTokenContext是用于存储当前待分析网页全局属性的数据结构,其中TokenList是核心的元素节点链表。词法分析的目的就是生成这样一个链表。下面给出该链表的数据结构,是很简单的双向链表。
typedef struct TokenList
{ BitToken *token; //元素节点
struct TokenList *priou;
struct TokenList *next;
}BitTokenList,*BitPTokenList;
以下是元素节点的数据结构:
typedef struct BitToken
{int type; //节点类型,如定义的HTML_BODY,HTML_TXT等。
char *pData; //如果是HTML_TXT型元素,则为其内容,否则为空
BOOL end; //是否是结束元素,如</body>
BitTokenAttrList *attrList; //元素属性链表,因为可能有多个属性,所以使用链表存储
BitTokenAttrList *attrList_tail;
}BitToken,*BitPToken;
请注意,以上出现tail标记的指针变量,如BitTokenList * tokenList_tail等,其作用是用于保存链表结尾节点指针,便于在释放内存时,直接找到链尾,提高了算法的效率。
2.3 算法
2.3.1 基本算法:
首先介绍基本的算法:
(1) 从存储网页的字符串中,顺序读入一个字符
(2) 如果遇到 < ,认为遇到TAG(元素),处理该元素,使用函数Token_ConsumTag(),处理完毕后,指针移到该元素尾。
(3) 如果遇到回车、空格,则跳过。
(4) 如果遇到 > ,则跳过(不应该出现此情况,为了容错)。
(5) 如果非以上情况,则认为遇到文字,处理这段文字,使用函数Token_Consum_PlainText()。处理完毕,指针指向下一个元素首。
(6) 循环以上操作,直到该网页分析完毕。
由此看来,主算法十分简单而清晰,主要是Token_ConsumTag()和Token_Consum_PlainText()这两个函数起关键作用,由于其中涉及到许多细节问题,此处不予详述。
2.3.2 算法效率与改进:
采用以上的基本算法,是可用的,但当网页比较大的时候,比如600K,该算法的效率成倍下降,这主要是由于要处理的字符串太大,在内存中完成查找、替换、复制、移动等操作,响应时间明显下降。对此的改进办法就是分段进行词法分析,不仅极大的提高了效率(在某些情况下约提高30倍),也有利于浏览器整体设计,因为当网页较大时,若等待全部内容传输完毕,再一次性完成词法分析和布局,用户会感到等待时间过长,一般现在成熟的浏览器都采用边传输,边分析,边显示。
分段进行词法分析的算法复杂度明显增加,比如,当每段定为1024字节,在第1024字节处,可能正好将一个完整元素截断,按常规分析方法会造成错误。解决的办法是,采用回溯,确认要分析的部份至少包含1个完整元素。
具体做法是:判断1024字节处是否为元素结束字符 ‘>’,如果不是,则判断前一个字节,直到找到元素结束字符为止,这样可保证至少包含一个元素。
采用分段进行词法分析,实际每次分析的代码会不足1024字节,余下的部份汇入到下一段的分析过程即可,直到所有内容被分析完毕。
2.4词法分析的结果
下面是一段很简单的HTML代码。
<html>
<img src=“go.gif” width=200 height=100>
<a HREF="http://www.263.net">首都在线</a>
</html>
分析后,数据存储结构如下
:
可以看到,词法分析的结果是一个元素节点链表,每个节点的属性也形成了一个链表,元素节点是有先后顺序的,元素属性的先后顺序是无所谓的。
词法分析将网页的文本数据流以清晰的结构表现出来,这样,在后面的应用中就可以很容易的遍历各节点,并轻松地获得各元素节点的属性。
2.5 HTML词法分析的应用
2.5.1 应用举例:
HTML词法分析程序通常应用于浏览器设计、网页制作软件设计等领域,本人以一个使用VC开发的软件“HTML智能分析”来举例说明,下载网址:
http://netbit_browser.myetang.com/introduce.html。
“HTML智能分析”同样使用Bit Token词法分析器,“HTML智能分析”是一个网页信息提取、处理软件。
具有以下主要功能:
1、智能提取网页中的文字信息,智能排版,并可在进行编辑后保存。
2、统计网页的有关信息。
3、根据用户设置的版式,将分析和编辑的结果,自动生成新的网页。
用户可使用该软件来将HTML转为TXT格式,其对HTML中文字内容的提取准确、快速、不含冗余信息,版式工整清晰,保持本来面貌。
其主要设计思路是,在Bit Token词法分析器的基础上,结合浏览器布局的基本算法,对影响到TXT版面效果的元素进行处理。
比如<PRE>标记,代表所包含的内容浏览器应不予分析,按TXT格式输出,而如表格
等元素则意味着需要换行。而在HTML中,在无<PRE>这种特殊情况时,回车都是忽略不记的。这就造成了矛盾。使用常规的简单算法进行HTML到TXT的转换无法解决这些问题。造成转换后的版式“失真“。而“HTML智能分析”却能很好的解决。
由于“HTML智能分析”使用了底层的词法分析技术,还可以很容易的过滤掉<SCRIPT>与<STYLE>(样式表)。并可以对网页中的元素进行统计和语法校验。
以下是该程序的片断:
pTtokenList=global_cx->tokenList; //取首节点
while(pTtokenList!=NULL) //循环直至处理完所有节点
{
switch(pTtokenList->token->type)
{//根据节点类型,做不同的处理
case HTML_TITLE: ……
case HTML_TEXT: ……
default: ……
} //switch
pTtokenList=pTtokenList->next; //取下一个节点
} //while
这段程序实际上就是一个简单的语法分析和布局的过程。
2.5.2 Bit Token在应用中存在的问题及修改意见
由于HTML的标记多是成对出现的,并且存在<SCRIPT>这样的特殊元素,其内容为Javascript程序,函数的字符串参数等可能包含其它的元素标记。例如语句:Alert(“<font> is a tag”);
因此,在词法分析时要对<SCRIPT>标记进行特殊处理,遇到<SCRIPT>就应逐字符读入后面的内容,直到遇到下一个</SCRIPT>标记。目前的Bit Token由于开发时间所限,未对其加以特殊处理,存在一些问题,但由于浏览器对Javascript的支持是较复杂的工作,目前的Netbit Browser尚不予实现,因而没有导致明显问题,而“HTML智能分析”这个软件只是需要对Javascript进行删除操作,也不会造成影响。尽管如此,对<SCRIPT>的特殊处理还是有待完善,尽管这同时也会带来一些问题,需要进行大量的测试,来保证新加入代码的稳定性。
正如前面所述,HTML词法分析是浏览器设计的基础环节之一,但并非最重要和最具难度的环节,若想开发出效果较好的浏览器产品,还要在布局和GUI设计上多下功夫。
第三章 浏览器JavaScript支持的实现
本部份主要针对Mozilla和Netscape浏览器源代码的JavaScript部份进行了分析,阐述了浏览器Javascript实现的机制。
3.1基本的JavaScript 开发环境
JavaScript Reference与JavaScript API:
JavaScript Reference是Mozilla所使用JavaScript开发环境,是使用ANSI C的独立的开发包,据Mozilla文档介绍,该开发包涉及到超过160家公司的版权。而且被广泛使用,实际已成为了进行JavaScript应用开发的标准平台。
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,我们实际开发JavaScript应用,也是在JavaScript API基础上工作,而不用过多考虑其内部的实现。关于JavaScript API,参见JavaScript API详解。JavaScript API实际就是Javscript解释器的对外接口函数库。
3.2 JavaScript Engine
JavaScript Engine是浏览器开发者为了利用JavaScript API来实现实际应用而设立的中间层,用于初始化JavaScript环境,提供对JavaScript解释、执行的接口。浏览器主体程序的设计者可以通过JavaScript Engine,方便的实现各种应用,毕竟JavaScript API太基础了,直接使用不太方便。
下面介绍JavaScript Engine的主要功能和实现方法。这也包含了利用JavaScript API进行应用的基本思路。
(1) 初始化:
内存分配:rt=JS_Init(10000L);
初始化cx:cx = JS_NewContext(rt, STACK_CHUNK_SIZE);
初始化globalObj:globalObj = JS_NewObject(cx, &globalClass, 0, 0);
定义标准类:JS_InitStandardClasses(cx, globalObj);
定义系统函数:JS_DefineFunctions(cx, globalObj, g_functions);
定义报错函数: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解释、执行的接口函数:
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>
当鼠标移到文字上时,触发事件mouseover,调用ChangeImage()函数,使得图像源(SRC)发生变化,重新调入新图片。
由此产生两个关键问题:
1. javascript如何获取HTML元素的名称和属性。
2. javascript如何改变HTML元素的属性,并操作WIDGET重画。
下面分别阐述这两个问题:
首先介绍涉及到的浏览器流程:
问题1解决:HTML元素作为Javascript对象进行注册。
注册过程在BuildModel中进行。BuildModel的首要任务是将Token后的结点按包含关系展成一棵树。其次就是要将某些结点注册为JavaScript对象。
注册的过程是:
定义新对象:JSObject *proto;
初始化该对象:
TD_JSXMLElementClassInit(JS_GetGlobalContext(),
(void **)&proto))
使用JS_DefineObject或JS_NewObject定义对象属性:
根据是否定义了该元素的名称区别对待:
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解决:利用注册给对象的函数实现操作符的功能化。
具体可理解为:当image0.src=”1.gif”被执行时,相当于为对象设置或改变属性,此时SetElementProperty函数被调用(该函数在注册该对象时由JSXMLElementClassInit捆绑给该对象,其内容由用户自己定义),SetElementProperty通过函数指针调用函数TD_JSXMLSetAtrByID,改变结点树上结点属性,并重新生成该节点对应的widget,重画界面。
问题3:如何建立Javascript对象与结点树上结点的对应?
解决: Javascript对象与结点树是同时生成的,它们的共同性质是结点具有相同属性,Javascript对象根据ID属性查找树,找到要操作的对应结点。
3.4 浏览器消息响应
在主消息循环中调用TDWidgetProcessMsg,处理与widget有关消息。
首先:取得当前焦点所在的widget
pWidget=TDWidgetGetAtPoint(pThis->baseDoc.base.mWidget,pt,&index);
处理该widget对该消息的响应。
最后一般为调用JavaScript执行,实现实际响应。
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源码,解释执行的结果就是调用为该对象注册的函数来重画该widget,从而实现动态效果。