摘要:本文分析WebKit中html的解析過程,DOM節(jié)點樹的建立。 關(guān)鍵詞:WebKit,html解析,html tree construction,WebCore, DOM節(jié)點樹,dlmu2001 1. HTML解析模型圖1 HTML解析模型圖 上圖是HTML解析模型圖,HTML解析分成Tokeniser和Tree Construction兩個步驟,在”WebKit中的html詞法分析” (http://blog.csdn.net/dlmu2001/archive/2010/11/09/5998130.aspx)一文中,我們已經(jīng)對Tokeniser這一步進(jìn)行了分析,本文的目標(biāo)是Tree Construction這一步。 Tree Construction輸入是token流,輸出是DOM節(jié)點樹。 2. DOM樹HTML DOM定義了一套標(biāo)準(zhǔn)來將html文檔結(jié)構(gòu)化,它定義了表示和修改文檔所需的對象、這些對象的行為和屬性以及對象之間的關(guān)系,可以把它理解為頁面上數(shù)據(jù)和結(jié)構(gòu)的一個樹形表示。 Node是DOM模型中的基礎(chǔ)類,它可以分成13類(見NodeType),在HTML解析中,最常見的是Document,Element,Text三類。 l Document是文檔樹的根節(jié)點,在HTML文檔中,他派生為HTMLDocument。 l 在文檔中,所有的標(biāo)簽轉(zhuǎn)化為Element類,一般它有標(biāo)簽名,并根據(jù)標(biāo)簽名繼承為特定的子類。 l Element之間的原始文本轉(zhuǎn)化成Text類。 以一個簡單的html頁面為例: <html> <head> <title>test</title> </head> <body> <h1>hl1</h1> <h2>hl2</h2> <h3>hl3</h3> </body> </html> 經(jīng)過解析后的節(jié)點樹如下(忽略換行符): 圖2 HTML DOM節(jié)點樹示例 如果沒有忽略換行符,則每個換行符就是一個Value為”\n”的Text節(jié)點。 3. Tree Construction原理將圖二中的節(jié)點樹以WebKit中的類具體化(同樣忽略換行符)。 圖3 Webkit HTML DOM節(jié)點樹示例 看到這里,你是不是覺得仿佛看到了一個呼之欲出的Tree Construction輪廓?是的,最簡化的情況就是這樣,根據(jù)輸入的token,創(chuàng)建出相應(yīng)的Element派生類,然后添加到DOM樹中合適的位置,這就是Tree Construction干的事情。當(dāng)然,添加到合適的位置,這個需要一系列復(fù)雜的規(guī)則,另外,WebKit將Render樹的創(chuàng)建也放到了Tree Construction階段中來,再加上CSS,Javascript,所以,這就是你看到的復(fù)雜的代碼。 放出兩個函數(shù)原型,熱熱身,培養(yǎng)培養(yǎng)感情。
Tree Construction流程由一個狀態(tài)“Insertion Mode”進(jìn)行控制,它影響token的處理以及是否支持CDATA部分,HTML5中給出了詳細(xì)的規(guī)則(http://www./specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode)。它也控制了在特定狀態(tài)下能夠處理的token,比如在head里面,再出現(xiàn)head標(biāo)簽,顯然是不應(yīng)該處理的。 4. 開放元素堆棧為了維護(hù)即將解析的標(biāo)簽同已解析的標(biāo)簽之間的關(guān)系(此時即將解析的標(biāo)簽還沒有加入到DOM樹中),引入了開放元素堆棧m_openElements,初始狀態(tài)下,這個堆棧是空的,它是向下增長的,所以最上面的節(jié)點是最早加入到堆棧中的,在html文檔中,最上面的節(jié)點就是html元素,最底部的節(jié)點就是最新加入到堆棧中的。Tree Builder的時候,每碰到一個StartTag的token,就會往m_opnElements中壓棧,碰到EndTag的token,則出棧。像Character這樣的token,則不需要進(jìn)行壓棧出棧的動作,只有可以包含子節(jié)點的tag,才做壓棧出棧的動作。Html5的文檔中對開放元素堆棧也有說明,http://www./specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements。 對于正在解析的token,除了根節(jié)點html,它必然是堆棧底部元素(m_openElements.top())的子節(jié)點,所以在形成DOM樹的時候,就可以通過ContainerNode::parserAddChild這樣的接口加入到DOM節(jié)點樹中。 除了正常的堆棧和壓棧,對于html,head,body元素,棧結(jié)構(gòu)(HTMLElementStack)中有專門的成員m_htmlElement,m_headElement,m_bodyElement記錄,主要是用于檢錯糾錯處理。 在本文的html范例中,當(dāng)解析到<h2>hl2</h2>的hl2這個character的token的時候,它的開放元素堆棧如下,HTMLHeadingElement是堆棧的top,所以它是hl2這個Text節(jié)點的parent。 圖4 開放元素堆棧示例 此時的DOM節(jié)點樹如下: 圖5 Webkit DOM節(jié)點數(shù)示例 5. 元素的創(chuàng)建HTMLElementFactory類提供了元素的創(chuàng)建方法createHTMLElement。傳入為對應(yīng)的標(biāo)簽名,所屬的document,所屬的form(如果屬于form),在parser的時候,最后一個參數(shù)為true。
在HTMLElementFactory中,通過一個Hash Map將tag name和對應(yīng)的元素構(gòu)造函數(shù)對應(yīng)起來(gFunctionMap)。tag一般對應(yīng)一個派生于HTMLElement的類。如下是HTMLHeadingElement的類層次結(jié)構(gòu)圖。 圖6 HTMLHeadingElement類層次圖 6. 其它HTMLConstructionSite::attach中的attach一詞,地瓜理解主要是attach到DOM節(jié)點數(shù)上,當(dāng)然,它同時調(diào)用了Element::attach,Element類的attach主要是attach到Render樹上,它會創(chuàng)建對應(yīng)該Element的RendrObject。 除了m_openElements,HTMLConstructionSite同時維護(hù)了Format 元素列表m_activeFormattingElements,Formating元素就是那些格式化標(biāo)簽,包括a,b,big,code,em,font,I,fot,I,nobr,s,small,strike,strong,tt,u。為了處理這些Formatting元素的嵌套關(guān)系(此時它們可能不是父子關(guān)系,而是平級,不加入到m_openElements),HTML5引入了這個列表(http://www./specs/web-apps/current-work/multipage/parsing.html#list-of-active-formatting-elements)。 使用gdb調(diào)試的童子,可以運行Tools/gdb/webkit.py腳本,在print結(jié)構(gòu)體的時候得到易于理解的表示,還可以打印出節(jié)點樹,具體參考http://trac./wiki/GDB。 |
|