【zz】HTML词法分析器的设计及其应用

第二章 HTML词法分析器的设计及其应用

 

HTML词法分析是浏览器设计的基础环节之一,也是整个设计过程中重要的前端工作,其数据结构的拟定与接下来的语法分析和布局算法密切相关,词法分析的效率与准确性、容错性也关系到整个浏览器设计的质量。

 

下面将介绍一个HTML词法分析器——Bit Token的设计思路。

 

Bit TokenNetbit BrowserHTML词法分析器,使用标准C编程,Netbit Browser是基于Linux/Gtk的浏览器,开放源码项目,网址是http://netbit_browser.myetang.com

 

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_BODYHTML_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>

分析后,数据存储结构如下

 

<img>

<a>

text

</a>

src

go.gif

width

200

height

100

href

http://www.263.net

data

首都在线


 

可以看到,词法分析的结果是一个元素节点链表,每个节点的属性也形成了一个链表,元素节点是有先后顺序的,元素属性的先后顺序是无所谓的。

 

词法分析将网页的文本数据流以清晰的结构表现出来,这样,在后面的应用中就可以很容易的遍历各节点,并轻松地获得各元素节点的属性。

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格式输出,而如表格<TR>等元素则意味着需要换行。而在HTML中,在无<PRE>这种特殊情况时,回车都是忽略不记的。这就造成了矛盾。使用常规的简单算法进行HTMLTXT的转换无法解决这些问题。造成转换后的版式“失真“。而“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支持的实现

 

本部份主要针对MozillaNetscape浏览器源代码的JavaScript部份进行了分析,阐述了浏览器Javascript实现的机制。

3.1基本的JavaScript 开发环境

JavaScript ReferenceJavaScript API:

JavaScript ReferenceMozilla所使用JavaScript开发环境,是使用ANSI C的独立的开发包,据Mozilla文档介绍,该开发包涉及到超过160家公司的版权。而且被广泛使用,实际已成为了进行JavaScript应用开发的标准平台。

 

JavaScript Reference可以用于建立包含JavaScript runtimeLibrary DLL。既可以编译成小的 "shell" 程序(像早期的BASIC),又连接Library后生成交互式的JavaScript解释器,也可以用来解释.js 文件。由于使用了ANSI C编程,可以用VCGCC等编译器在不同平台下编译。

 

生成的"shell" 程序,对比浏览器对JavaScript的支持,相同之处是使用相同的包含JavaScript runtimeLibrary  DLL,我们把这部份相同的LibraryDLL称为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);

初始化cxcx = JS_NewContext(rt, STACK_CHUNK_SIZE);

初始化globalObjglobalObj = 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 οnmοuseοver="ChangeImage(0);">军人</a><br>

<a οnmοuseοver="ChangeImage(1);">眼睛</a><br>

<img id="image0" src="a0.gif"></img>

</html>

当鼠标移到文字上时,触发事件mouseover,调用ChangeImage()函数,使得图像源(SRC)发生变化,重新调入新图片。

 

由此产生两个关键问题:

1.   javascript如何获取HTML元素的名称和属性

2.   javascript如何改变HTML元素的属性,并操作WIDGET重画

 

下面分别阐述这两个问题:

首先介绍涉及到的浏览器流程:

 

PARSE

Tokenize

BuildModel

BuildPres

ProcessElement

WidgetPaint

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


问题1解决:HTML元素作为Javascript对象进行注册

注册过程在BuildModel中进行。BuildModel的首要任务是将Token后的结点按包含关系展成一棵树。其次就是要将某些结点注册为JavaScript对象。

 

注册的过程是:

定义新对象JSObject *proto;

 

初始化该对象

TD_JSXMLElementClassInit(JS_GetGlobalContext(),

 (void **)&proto))

 

使用JS_DefineObjectJS_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,从而实现动态效果。


第五章 Netbit浏览器开发与分析

5.1 Netbit 浏览器简介

Netbit Browser 是基于Linux平台的浏览器,使用了gtk作为gui开发工具。项目的目的是要建立一个小型的、快捷的web浏览器,并便于移植到嵌入式系统中。该项目是开放源码项目,由sogo456@263.net负责维护,网址是:http://netbit_browser.myetang.com

 

项目目前已完成 0.0.1 demo版,实现了基本界面,html4.0词法分析、支持本地文本文件查看,英文网页浏览(使用菜单open file),支持简单http连接,支持URL访问,如输入http://www.gtk.org/download/top.htm等,支持字体颜色、字号等基本的元素。

 

Netbit Browser在界面上主要承袭了Gzilla的风格,在技术上主要涉及了词法、语法分析,文档布局,PIXMAP画图,文件IO操作,简单HTTP访问等技术。

 

界面模块

控制模块


 

 

PIXMAP画图模块


5.2 Netbit Browser 浏览器各部份的功能

 

 

IO模块

词法分析模块

文本文件显示模块

HTML文件显示模块


 

(1)主程序:用于初始化。

(2)界面模块:窗口、菜单、工具条、滚动条等的初始化。

(3)控制模块:负责命令的响应,消息的处理,是软件控制的中枢。

(4)IO模块:包括http和文件操作。

(5)词法分析模块:HTML的词法分析。

(6)文本文件的显示模块:对Plain text显示的处理,也包含相应的布局算法。

(7)HTML文件的显示模块:在Netbit browser中使用了语法分析与布局同时进行的方式,由此模块来驱动HTML文件的显示。

(8)使用PIXMAP的画图模块:是实际输出采用的方法,被67模块调用。

 

以下将就主要的较复杂的模块进行说明。

 

5.3 界面模块

(1) interface.cinterface.h:用于界面的初始化和定制。

主要的函数说明:

void a_Interface_init(void);界面初始化的主函数

void a_Interface_status(BrowserWindow *bw, const char *format, ... );

设置状态栏内容。

void a_Interface_openfile_dialog(BrowserWindow *bw);文件打开对话框

void a_Interface_set_Page_title(BrowserWindow *bw, char *title);

设置窗口标题。

void a_Interface_entry_open_url(GtkWidget *widget, BrowserWindow *bw);

打开网址输入栏输入的网址。

BrowserWindow *a_Interface_new_browser_window(gint width, gint height);

新建浏览窗口,是很重要的函数,在其中创建了所有的界面控件,并对按钮等进行了消息绑定。

 

(2) menu.cmenu.h

主要函数:GtkWidget *a_Menu_mainbar_new (BrowserWindow *bw);

定义了主菜单项,并进行了消息绑定

 

(3) browser.h

定义了重要的窗口结构如下:

struct _BrowserWindow

{

   /* 主窗口的widgets */

   GtkWidget *main_window;

   GtkWidget *back_button;

   GtkWidget *forw_button;

   GtkWidget *stop_button;

   GtkWidget *location;

   GtkWidget *location_button;

   GtkWidget *status;

 

   /* 键盘控制表*/

   GtkAccelGroup *accel_group;

 

   /* 工具条按钮 */

   GtkWidget *back_menuitem;

   GtkWidget *forw_menuitem;

   GtkWidget *stop_menuitem;

 

   /* 主文档 widget. (用于绘制HTML或其它) */

   GtkWidget *layout;

 

   /* 当前光标类型 */

   GdkCursorType CursorType;

 

   /* 对话框widgets*/

   GtkWidget *open_dialog_window;

   GtkWidget *open_dialog_entry;

   GtkWidget *openfile_dialog_window;

   GtkWidget *quit_dialog_window;

 

   /* 指向保存词法分析结果的数据结构 */

   BitTokenContext *global_cx;

 

   /* 文件类型:html or plain text*/

   gint file_type;

};

 

5.4 控制模块

(1)command.ccommand.h

主要的函数说明:

void a_Commands_openfile_callback (GtkWidget *widget, gpointer client_data);打开文件的对话框

void a_Commands_openurl_callback (GtkWidget *widget, gpointer client_data);打开URL

void a_Commands_close_callback(GtkWidget * widget, gpointer client_data);关闭窗口

void a_Commands_exit_callback (GtkWidget *widget, gpointer client_data);退出程序

void a_Commands_viewsource_callback (GtkWidget *widget, gpointer client_data);查看HTML源码

void a_Commands_reload_callback (GtkWidget *widget, gpointer client_data);刷新当前网页

void a_Commands_home_callback (GtkWidget *widget, gpointer client_data);显示主页

void a_Commands_helphome_callback (GtkWidget *widget, gpointer client_data); 显示帮助

 

(2)nav.hnav.c:是命令对应的与网页操作有关的具体实施

 

主要的函数说明:

void a_Nav_push(BrowserWindow *bw, const char*);URL打开一个网址或文件,具有对不完整URL的兼容性。

void a_Nav_reload(BrowserWindow *bw);刷新当前网页

void a_Nav_open_splash(BrowserWindow *bw,char *str);打开起始页(内置页面)

 

5.5词法分析模块

词法分析的原理和算法在前面已有详述。

(1)    BitToken.cBitToken.h

主要的函数说明:

BitTokenContext * Bit_NewContext(); 创建新的全局结构

int Bit_Tokenize(BitTokenContext *global_cx); 局部词法分析

void Bit_BeginToken(BitTokenContext *global_cx); 全局词法分析

int Bit_DestroyToken(BitTokenContext *global_cx); 释放内存

char *Token_ReadUntil(BitTokenContext *global_cx,char *sUntil);重要的字符处理函数,读取到指定字符后结束

char *Token_GetAttribute(BitTokenContext *global_cx); 取元素属性

void Token_ConvertIfNeed(char * aString); 转义字串的处理

int Token_ConsumTag(BitTokenContext *global_cx);处理元素

int Token_Consum_PlainText(BitTokenContext *global_cx); 处理文本

void Bit_ShowTokenResult(BitTokenContext *global_cx);显示分析结果

void Bit_SaveTokenResult(BitTokenContext *global_cx,char * filename); 保存分析结果

 

(2)    BitHtmlDtd.hBitHtmlDtd.c

用于存储HTML4.0元素的名称和属性。

 

(3)    BitTokenList.hBitTokenList.c

元素链表相关

(4)    BitTokenAttrList.hBitTokenAttrList.c

元素属性链表相关

(5)    BitStr.hBitStr.c

字符串处理函数

 

5.6使用PIXMAP的画图模块

因本部份是HTML文件的显示模块、文本文件的显示模块的基础,所以先予说明。

 

paint.cpaint.h

主要的函数说明:

gint pixmap_new(GtkWidget *widget,int width,int height);

在此函数中使用

pixmap  = gdk_pixmap_new(widget->window,width+30,height+30,-1);来新建一个pixmap

 

gint expose_event (GtkWidget *widget, GdkEventExpose *event); expose消息到来时,即若界面被破坏需重画时,使用

gdk_draw_pixmap(widget->window,widget->style->fg_gc[GTK_WIDGET_STATE(widget)],pixmap,event->area.x,event->area.y,event->area.x,ent->area.y, event->area.width, event->area.height);来重画。

 

gint pixmap_repaint(GtkWidget *widget);用于提供手动重画。

gint Browser_Paint(BrowserWindow *bw);layout的主函数,用来根据文件类型来调用HTML文件的显示模块或文本文件的显示,同时初始化滚动条。

 

5.7 文本文件的显示模块

plain.cplain.h

char *Plain_handle_tabs(const char *str)TAB转为空格。

void a_Plain_write(GtkLayout *display,char *Buf1, gint BufSize)主要函数

下面介绍一下文本显示的算法。

指定默认字体

font=gdk_font_load("-adobe-helvetica-medium-r-normal--14-*-*-*-*-*-iso8859-1");

通过预布局来计算页面的长度:

while(i<BufSize) 

  { j=0;

    while(line_size<SCREEN_WIDTH-20 && Buf[i]!='/n')

    {str[j]=Buf[i];

     line_size+=gdk_char_width(font,str[j]);

     j++;

     i++;

    }

    str[j]='/0';   

    if(Buf[i]=='/n')i++;

    x=X_START;

    line_size=x;

    y+=16;

  }

创建PIXMAP

  pixmap_new(drawing_area,SCREEN_WIDTH,y);

  gc = gdk_gc_new(drawing_area->window);

进行真实的画图。

pixmap_repaint(drawing_area);

输出到PIXMAP并显示

 

5.8 HTML文件的显示模块

这部份是整个浏览器最重要的部份之一,综合了语法分析与HTML的布局、输出,其算法的好坏直接关系到网页的显示效果。

 

主要流程:

  while(pTtokenList!=NULL)

  {…………

   switch(pTtokenList->token->type)

        {

          case HTML_TITLE:

           …………

             break;

              

          case HTML_TEXT:

           …………

                 break;

    …………

       …………

          default:

       ………

           break;

} //switch

   pTtokenList=pTtokenList->next;

  } //while

 

可以看到,这部份与语词分析结合的十分紧密,利用词法分析的结果,遍历各元素节点,取出其元素属性,根据一定的布局算法来进行布局。

 

例如:当遇到title元素时,就使用gtk函数来设定窗口标题为指定标题

gtk_window_set_title(GTK_WINDOW(bw->main_window),pTtokenList->token->pData);

其中pTtokenList->token->pData即为词法分析分析出的标题内容。

 

由于程序结构十分简单清晰,大部份元素的处理都简单易懂,参考源程序即可,下面主要针对<font>和相关标记对字体的设置阐述其算法。

 

由于<font>标记允许嵌套,所以使用了栈来对font元素进行管理,例如以下的HTML代码:

<font size=4 color=#0000FF>

This program is not <b>free software</b>; you can redistribute it and/or

modify it under the terms of the <font size=5 color=FF0000>GNU General

Public License </font>as published by the Free Software Foundation;

either version 2 of the License, or (at your option) any later version.

</font>

 

显示的效果应为GNU General Public License的字号为5,颜色为FF0000即红色;free software应为粗体,受首尾两个呼应的font标记约束,其它字字号均为4,颜色为0000FF,由于free software只被<b></b>这一对加粗符号约束,所以其颜色应受首尾的font标记的约束,即应为0000FF

 

这种嵌套的约束方式带来了HTML元素管理的混乱,也容易产生冗余的HTML代码,但既然标准是这么定的,也只能想办法加以解决,固然现在随着样式表的广泛采用,font已面临寿终正寝,但仍然大量存在,特别在对字体的颜色的设置,使用font标记很方便。

 

栈式管理的主要算法详解:

void html_open_font(GtkWidget *widget,char * style_str,char *color_str,char *size_str,int html_element,int insert_to_list)

 

该函数用于指定当前的字体属性,其参数包括style,color,size,以及改变字体属性的元素的名称,int insert_to_list用于标记此字体属性是否入栈,通常是入栈的。

 

这样,在出现font或相关元素的首标记时,我们将词法分析的结果提取出来,即将其元素属性提取出来,作为参数传递给html_open_font函数,该函数将这些属性进行组合,设置成为当前字体属性,并入栈保存;在出现font或相关元素的尾标记时,出于保险(因为存在交错包含关系的元素),首先检验栈顶元素与正在处理的元素尾标记是否匹配(名称相同),如相同则出栈,并将栈内下一字体属性设为系统的当前字体属性。

 

出栈函数为void html_close_font(GtkWidget *widget,int html_element)

需要注意的是由于并不是所有的font元素都指定所有的属性,可能只指定其中的一个或一部份属性,因此在入栈时必须做这样的处理,即首先获取当前的字体属性,根据哪些属性发生了变化来组合新的字体属性,然后入栈。

 

所使用的栈的结构很简单,如下。

typedef struct _font_list{

int html_element;

char color_str[15];

char size_str[15];

char style_str[15];

}font_list;

此为font_list的类型定义,描述了字体属性的结构

 

font_list font_opening[50]; 定义一个数组作为栈的存储形式

int current_font=0; 定义一个整型变量,作为栈顶指针

 

如此,一个简单的数组就发挥了巨大的作用,配以一点点算法,就带来了丰富多彩的界面效果。

 

5.9 Netbit实际应用效果及比较

下图为Netbit browser运行时的界面,所打开的页面源代码如下:

<html>

<body>

<h1>

<font color=#FF00FF><b>Netbit Browser Version 0.0.1 Demo</b></font></h1>

<hr>

<h4>License</h4>

<p>

<font size=4 color=#0000FF>

This program is not <b>free software</b>; you can redistribute it and/or

modify it under the terms of the <font size=5 color=FF0000>GNU General

Public License </font>as published by the Free Software Foundation;

either version 2 of the License, or (at your option) any later version.

</font>

<hr>

<h3>Design based on GTK, by sogo and ce!</h3>

</body>

</html>

 

以下为主菜单

 

 

以下为工具条

 

输入网页的URL,即可进行访问。

 

以下为打开文件对话框

 

以下为查看HTML源码对话框

 

下面对比Netbit Browser,看看其它浏览器查看此网页的效果。

以下为KDE浏览该网页的效果

 

 

 

以下为GZILLA浏览该网页的效果,GZILLA对字体颜色的处理比较差,只有黑色的字体。对字号的支持也不好。

 

以下为Netscape显示该网页的效果,Netscape默认背景色是灰色。

 

IE查看该网页的效果,字体不同是由于IE设置的默认字体不同。

可以看到,在对简单英文网页的支持效果上看,Netbit Browser,已接近于成熟浏览器的水平,甚至优于一些小型的嵌入式浏览器如GZILLANetfront,但在复杂页面的显示上还有较大的差距。

 

可以得出的结论是,Netbit Browser 0.0.1 Demo 版已经具有了一定的实用价值,但要对其进行完善,工作量还很巨大。

 

对比NetscapeIE的漫长的开发历史和巨大的资金投入,Netbit Browser的未来依然生死未卜。

 

5.10 Netbit Browser的缺点分析及改进办法

基础的GUI设计上存在缺陷

Netbit Browser目前的页面输出实际上还采用了简单的画图机制,无法在主窗体内放置如按钮、编辑框、单选框等控件,也无法处理页面元素的消息响应,(Netbit Browser 0.0.1-013版已进行了改进),而使用GTK作为开发平台是完全可以实现这些要求的,GZILLA就是最好的实例,它通过对现有控件的组合,开发了自己的文档视图控件,实现了上述功能。但之所以目前没有采用先进的文档视控件,是因为要实现这样的有较强实用性的自画文档视图控件,是需要很大的工作量的,仅GZILLA为实现其核心的DW文档视图控件,就动用了超过7000行的代码,比Netbit Browser目前的总代码量还大。而Netscape由于考虑到支持多个GUI平台,还需要一个抽象的中间层文档视图控件,这个中间层也在万行以上。

 

改进措施

固然,能容纳百川,一触即发的文档视控件的开发是很繁重的工作,但原理却并不复杂。下面加以阐述。

 

大多数的GUI平台都提供了方便用户进行控件组合的机制,例如有的控件能包含其它的控件,通常称之为container(容器),以GTK为例说明其原理。

 

GTK控件是以流行的控件组件的观念来设计的。 不过依然是以C来写的。 比起用C++来说这可以大大改善可移植性及稳定性。 但同时这也意味著widget 作者需要小心许多实际操作上的问题。 所有同一类别的控件的一般声明 (例如所有的按钮控件)是放在 class structure。 只有一个这样的结构。 在这个结构中储存类别信号的声明。 要支撑这样的继承,第一栏的资料结构必须是其父类别的资料结构。例如GtkButton的类别的声明看起来像这样:

struct _GtkButtonClass
{  GtkContainerClass parent_class;
  void (* pressed)  (GtkButton *button);
  void (* released) (GtkButton *button);
  void (* clicked)  (GtkButton *button);
  void (* enter)    (GtkButton *button);
  void (* leave)    (GtkButton *button);
};

当一个按钮被看成是个container(容器)(例如当它被缩放时), 其类别结构可被传到GtkContainerClass, 而其相关的栏位被用来处理信号。

具体而言,比如我们使用一个基础的layout控件来作为我们自画的文档视图控件的基础控件,layout = gtk_layout_new(NULL, NULL);

 

接下来我们就可以使用gtk_layout_put函数将其它的允许被包含的控件放进去,就是这么简单,那难度在哪呢?其实,对于网页显示而言,能放进去多少个按钮、编辑框、单选框并不是最主要的,这很容易实现,只要采用了类似layout这样的基础控件,我们原则上可以组合出来很多种效果。我们迫切关心的是那些需要用画图方法来实现的页面元素,如文字、图片、表格、直线是如何产生的。下面加以阐述。

 

实际上,无论在什么情况下,我们要作画都需要合适的画布,要在可以作画的控件上才可以施展拳脚,drawing_area正是这样的控件,如此,我们只要将画画在drawing_area上,然后再使用gtk_layout_put函数将drawing_area放置到layout控件上,不就万事大吉了?不错,但只对了一半,原来drawing_area本身并没有实现自我重画的机制,当最小化窗口或打开对话框时,原有的界面就被破坏,只有进行重画才能恢复原貌,重画又是怎样实现的呢?原来,我们在将drawing_area放置于layout之前,是做了手脚的,使用以下函数

gtk_object_set_data(GTK_OBJECT(drawing_area), "layout", partp);

来将画在drawing_area上的信息对应的数据封装给了drawing_area控件。同时使用函数

gtk_signal_connect(GTK_OBJECT(drawing_area), "expose_event",           (GtkSignalFunc)render_line_event, NULL);

来将expose_event这个重画消息,捆绑给了drawing_area控件。render_line_event即是用于重画的函数,到了需要重画的时候,该函数取出封装给drawing_area的数据进行重画。

 

如此这般费神,终于可以让线条、文字、表格、图片得见天日,完美的展现在众人面前(当然,所有的画与重画的函数都要自己写好了才行)。不要高兴太早,超级链接都不可点击,GIF动画也不会动,界面还是死的,原来忘了画龙点睛,怎么办?加消息。

 

加消息的步骤通常如下,以文字的超级链接为例。

首先新建一个消息盒子,event_box = gtk_event_box_new();

如果着急的话先把盒子放到layout上,当然用gtk_layout_put函数。若是最后再放上去,效果是同样的。

drawing_area装到盒子里。

gtk_container_add(GTK_CONTAINER(event_box),drawing_area);

 

下面指定消息:

if(partp->parent && partp->parent->type == LAYOUT_PART_LINK) {

    gtk_signal_connect(GTK_OBJECT(event_box), "enter_notify_event",

              (GtkSignalFunc)activate_text_link, drawing_area);

    gtk_signal_connect(GTK_OBJECT(event_box), "leave_notify_event",

              (GtkSignalFunc)activate_text_link, drawing_area);

    gtk_signal_connect(GTK_OBJECT(event_box), "button_press_event",

              (GtkSignalFunc)select_text_link, drawing_area);

    gtk_signal_connect(GTK_OBJECT(event_box), "button_release_event",

              (GtkSignalFunc)select_text_link, drawing_area);

    gtk_widget_add_events(event_box,

             GDK_ENTER_NOTIFY_MASK |

             GDK_LEAVE_NOTIFY_MASK |

             GDK_BUTTON_PRESS_MASK |

             GDK_BUTTON_RELEASE_MASK);

gtk_widget_realize(event_box);

 

如此这般,超级链接终于可点了(同样,消息引发的函数都得老老实实写好了)。可以实现移到上面就自动变色,点击就跳转到其它网页。

 

别忘了,指定一个漂亮的小手,这样点超级链接的时候更有情调。

gdk_window_set_cursor(event_box->window, gdk_cursor_new(GDK_HAND2));

 

    综上所述,如果每一个消息对应的函数都能按部就班的完成,超级自画文档视图控件的完成也就指日可待了。

 

若只支持简单的字、线、图,程序大致可以控制在千行以内,当然这不包括文字布局算法、图像解码算法的部份。除此之外,我们还要拟定比较完善的数据结构来存储我们画在drawing_area上的信息,通常这些信息是语法分析的结果,也可以是词法分析的结果,在自画过程中融入语法分析的过程。

 

5.11 Netbit Browser未来的展望

    Netbit Browser作为一个毕业设计课题,还是接近圆满的。通过Netbit Browser的开发,对常见的浏览器开发技术有了深入了解和实践机会,但若作为实际的软件开发项目来讲,还差的很远,为了使其能继续得以生存,决定将其变为开放源码项目,遵循GPL协议开发,现已成为中国Linux论坛(http://www.linuxforum.net)的MyLinux计划的一个子项目。

   

    下面主要讨论一下Netbit Browser在技术上亟待解决的问题。

(1)超级链接的支持,实际上在对技术的了解的基础上。如前面的详细分析可见,该部份的实现已没有障碍,应该尽快实现,根本上是个编程序的毅力问题。

 

(2)图片的显示图片显示因为有很多开放的开发包或源程序,只是显示的话难度不大,但要考虑到多个图片的同时传输、JPG的逐步显示、GIF的动态显示就愈显复杂,仍需要对图像算法、网络传输、线程、消息等技术多加研究。

 

(3)TABLE等复杂的元素的布局,这实际是一个众多浏览器共同的问题,目前除了IE以外,其它浏览器都还存在较大的问题,如果能实现类似Netfront的效果,就比较实用了。开放源码的ZEN浏览器的table布局算法也很值得借鉴。

 

市场展望:Netbit Browser使用标准C编程,可以移植到Windows平台。也可以利用相关技术实现有市场价值的应用,如“HTML智能分析”这个软件,就是利用了Netbit Browser的词法分析技术进行了有益的尝试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值