此系列的文章用于记录Litecore(http://sourceforge.net/projects/litecore)的开发过程,其中所提到的内容可能会因为认识上的错误或设计上的失误与不足在以后的开发中被否定掉。该系列文章不讨论所谓的市场需求及展望,只涉及C++/HTML/CSS/Javascript。
2011-07-12
Litecore将被实现为解析HTML、CSS和Javascript的网页排版引擎。废话少说,开始动手。
框架设计
从打开HTML文档到最后的显示的流程大致可以制定为。解析HTML文档并将元素解析成树结构,树结构中的部分元素可能会被调整,这是由于HTML文档编写者犯下的错误,接着对这颗树中BODY元素的子树进行排版,最后就是渲染工作。因此Litecore被划分为下面几个模块。
tokenizer: 词法分析,将HTML理解为一断token序列。
page_builder: 处理token并把分析出来的元素建立一个树。然后扫描节点,对需要调整的节点进行处理。
page_typesetter: 对树中的元素排版,确定各个元素的逻辑坐标。
page_renderer: 渲染树中的BODY元素,也就是我们将看到的网页效果。
page_widget: 实现窗口。
现阶段只需要关心这几个模块,例如HTTP/CSS/Javascript将在以后提及。
抽象HTML模型
HTML中的各个元素是由<Element Attr,…></Element> 标记,而且不同的元素有不同的特征,我们需要抽象这些特定的特征,以便page_builder在建立元素树的时候使用。
enum id{null, end_tag, global, a, body…};
此类型定义了一些由HTML定义的基本元素,树的每个节点都指明该节点的元素类型即id。
namespace categ { enum t{block, text_inline}; t what(id element); t contents(id element); }
抽象元素的类型,HTML规定了两种元素类型:block_level和text_level,这两种类型有不同的行为,例如block_level必须换行绘制元素,text_level不能包含block_level子类型,等等。其中what()函数返回一个元素的类型,contents()返回指定元素能包含的子元素类型。
namespace closeable { enum t{required, optional, forbidden}; t what(id element); }
抽象一个元素的关闭状态,例如,HTML定义IMG元素的关闭标签为forbidden,在解析IMG标签的时候,当开始标签结束,则该元素结束,因此不会把该元素后面的内容误解为IMG的子元素。当遇到IMG的关闭标签时,则可以直接忽略掉。
bool is_similar_containable(id element);
判断指定的元素是否能包含同类的元素,判断规则基于HTML规范的定义。
bool is_decorative_element(id element);
该函数抽象了一种元素类型。例如A,B这两种元素,如果不给出关闭标签,也不会破坏往后HTML的逻辑结构,而且在这类元素后的其他元素都具有该元素指定的特性。需要注意的是这种类型并非HTML由定义。
在前面一段中提到树的数据结构,因此这里不得不说一下树节点的定义。树结构的定义属于page_builder模块,定义如下:
template<typename ExtType> struct node { typedef ExtType ext_type; ext_type ext; elements::id kind; //id即上面提到的预先定义的元素类型 union ukind { elements::def::element_interface * element_if; elements::def::global_attr_element * global; elements::def::body * body; //等等其他的元素 }u; struct leaf_t { node * root; node * next; node * child; }leaf; };
node是一个类模板,原因很简单,ext保存了一些额外的信息,与page_builder毫不相干,为了降低复杂度,不必把不相干的信息引入到page_builder中。ext的具体内容由page_typesetter确定,并供page_typesetter使用。
node包含了一个union对象,其中最基础的类型可以通过element_if这个指针访问,因为所有具体的元素类型都派生自element_interface。要获取具体元素的信息则通过kind定义的类型来访问对应的指针。简单上说,就是通过switch语句。这里看上去可以用面向对象的多态来解决从而避免switch,但实际情况是,在当前环境下使用多态会带来不好的设计,因为元素的类型并非设计成class,它们只是struct而已。对元素的处理由其他的模块提供,而非在定义元素类型的时候就把所能预见方法统统打包挤进元素之中。
<body> <p> Hello, This is <img src="litecore.bmp"> - a html engine. </p> </body>