自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手寫簡易瀏覽器之Html Parser 篇

系統(tǒng) 瀏覽器
這篇是簡易瀏覽器中 html parser 的實(shí)現(xiàn),少了自閉合標(biāo)簽的處理,就是差一個(gè) if else,后面會(huì)補(bǔ)上。

 [[403967]]

本文轉(zhuǎn)載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉(zhuǎn)載本文請聯(lián)系神光的編程秘籍公眾號。

思路分析

實(shí)現(xiàn) html parser 主要分為詞法分析和語法分析兩步。

詞法分析

詞法分析需要把每一種類型的 token 識別出來,具體的類型有:

  • 開始標(biāo)簽,如 <div>
  • 結(jié)束標(biāo)簽,如 </div>
  • 注釋標(biāo)簽,如 <!--comment-->
  • doctype 標(biāo)簽,如 <!doctype html>
  • text,如 aaa

這是最外層的 token,開始標(biāo)簽內(nèi)部還要分出屬性,如 id="aaa" 這種。

也就是有這幾種情況:

第一層判斷是否包含 <,如果不包含則是 text,如果包含則再判斷是哪一種,如果是開始標(biāo)簽,還要對其內(nèi)容再取屬性,直到遇到 > 就重新判斷。

語法分析

語法分析就是對上面分出的 token 進(jìn)行組裝,生成 ast。

html 的 ast 的組裝主要是考慮父子關(guān)系,記錄當(dāng)前的 parent,然后 text、children 都設(shè)置到當(dāng)前 parent 上。

我們來用代碼實(shí)現(xiàn)一下:

代碼實(shí)現(xiàn)

詞法分析

首先,我們要把 startTag、endTag、comment、docType 還有 attribute 的正則表達(dá)式寫出來:

正則

結(jié)束標(biāo)簽就是

  1. const endTagReg = /^<\/([a-zA-Z0-9\-]+)>/; 

注釋標(biāo)簽是 中間夾著非 --> 字符出現(xiàn)任意次

  1. const commentReg = /^<!\-\-[^(-->)]*\-\->/; 

doctype 標(biāo)簽是 字符出現(xiàn)多次,加 >

  1. const docTypeReg = /^<!doctype [^>]+>/; 

attribute 是多個(gè)空格開始,加 a-zA-Z0-9 或 - 出現(xiàn)多次,接一個(gè) =,之后是非 > 字符出多次

  1. const attributeReg = /^(?:[ ]+([a-zA-Z0-9\-]+=[^>]+))/; 

開始標(biāo)簽是 < 開頭,接 a-zA-Z0-9 和 - 出現(xiàn)多次,然后是屬性的正則,最后是 > 結(jié)尾

  1. const startTagReg = /^<([a-zA-Z0-9\-]+)(?:([ ]+[a-zA-Z0-9\-]+=[^> ]+))*>/; 

分詞

之后,我們就可以基于這些正則來分詞,第一層處理 < 和 text:

  1. function parse(html, options) { 
  2.     function advance(num) { 
  3.         html = html.slice(num); 
  4.     } 
  5.  
  6.     while(html){ 
  7.         if(html.startsWith('<')) { 
  8.             //... 
  9.         } else { 
  10.             let textEndIndex = html.indexOf('<'); 
  11.             options.onText({ 
  12.                 type: 'text'
  13.                 value: html.slice(0, textEndIndex) 
  14.             }); 
  15.             textEndIndex = textEndIndex === -1 ? html.length: textEndIndex; 
  16.             advance(textEndIndex); 
  17.         } 
  18.     } 

第二層處理 <!-- 和 <!doctype 和結(jié)束標(biāo)簽、開始標(biāo)簽:

  1. const commentMatch = html.match(commentReg); 
  2. if (commentMatch) { 
  3.     options.onComment({ 
  4.         type: 'comment'
  5.         value: commentMatch[0] 
  6.     }) 
  7.     advance(commentMatch[0].length); 
  8.     continue
  9.  
  10. const docTypeMatch = html.match(docTypeReg); 
  11. if (docTypeMatch) { 
  12.     options.onDoctype({ 
  13.         type: 'docType'
  14.         value: docTypeMatch[0] 
  15.     }); 
  16.     advance(docTypeMatch[0].length); 
  17.     continue
  18.  
  19. const endTagMatch = html.match(endTagReg); 
  20. if (endTagMatch) { 
  21.     options.onEndTag({ 
  22.         type: 'tagEnd'
  23.         value: endTagMatch[1] 
  24.     }); 
  25.     advance(endTagMatch[0].length); 
  26.     continue
  27.  
  28. const startTagMatch = html.match(startTagReg); 
  29. if(startTagMatch) {     
  30.     options.onStartTag({ 
  31.         type: 'tagStart'
  32.         value: startTagMatch[1] 
  33.     }); 
  34.  
  35.     advance(startTagMatch[1].length + 1); 
  36.     let attributeMath; 
  37.     while(attributeMath = html.match(attributeReg)) { 
  38.         options.onAttribute({ 
  39.             type: 'attribute'
  40.             value: attributeMath[1] 
  41.         }); 
  42.         advance(attributeMath[0].length); 
  43.     } 
  44.     advance(1); 
  45.     continue

經(jīng)過詞法分析,我們能拿到所有的 token:

語法分析

token 拆分之后,我們需要再把這些 token 組裝在一起,只處理 startTag、endTag 和 text 節(jié)點(diǎn)。通過 currentParent 記錄當(dāng)前 tag。

  • startTag 創(chuàng)建 AST,掛到 currentParent 的 children 上,然后 currentParent 變成新創(chuàng)建的 tag
  • endTag 的時(shí)候把 currentParent 設(shè)置為當(dāng)前 tag 的 parent
  • text 也掛到 currentParent 上
  1. function htmlParser(str) { 
  2.     const ast = { 
  3.         children: [] 
  4.     }; 
  5.     let curParent = ast; 
  6.     let prevParent = null
  7.     const domTree = parse(str,{ 
  8.         onComment(node) { 
  9.         }, 
  10.         onStartTag(token) { 
  11.             const tag = { 
  12.                 tagName: token.value, 
  13.                 attributes: [], 
  14.                 text: ''
  15.                 children: [] 
  16.             }; 
  17.             curParent.children.push(tag); 
  18.             prevParent = curParent; 
  19.             curParent = tag; 
  20.         }, 
  21.         onAttribute(token) { 
  22.             const [ name, value ] = token.value.split('='); 
  23.             curParent.attributes.push({ 
  24.                 name
  25.                 value: value.replace(/^['"]/, '').replace(/['"]$/, ''
  26.             }); 
  27.         }, 
  28.         onEndTag(token) { 
  29.             curParent = prevParent; 
  30.         }, 
  31.         onDoctype(token) { 
  32.         }, 
  33.         onText(token) { 
  34.             curParent.text = token.value; 
  35.         } 
  36.     }); 
  37.     return ast.children[0]; 

我們試一下效果:

  1. const htmlParser = require('./htmlParser'); 
  2.  
  3. const domTree = htmlParser(` 
  4. <!doctype html> 
  5. <body> 
  6.     <div> 
  7.         <!--button--> 
  8.         <button>按鈕</button> 
  9.         <div id="container"
  10.             <div class="box1"
  11.                 <p>box1 box1 box1</p> 
  12.             </div> 
  13.             <div class="box2"
  14.                 <p>box2 box2 box2</p> 
  15.             </div> 
  16.         </div> 
  17.     </div> 
  18. </body> 
  19. `); 
  20.  
  21. console.log(JSON.stringify(domTree, null, 4)); 

成功生成了正確的 AST。

總結(jié)

這篇是簡易瀏覽器中 html parser 的實(shí)現(xiàn),少了自閉合標(biāo)簽的處理,就是差一個(gè) if else,后面會(huì)補(bǔ)上。

我們分析了思路并進(jìn)行了實(shí)現(xiàn):通過正則來進(jìn)行 token 的拆分,把拆出的 token 通過回調(diào)函數(shù)暴露出去,之后進(jìn)行 AST 的組裝,需要記錄當(dāng)前的 parent,來生成父子關(guān)系正確的 AST。

html parser 其實(shí)也是淘系前端的多年不變的面試題之一,而且 vue template compiler 還有 jsx 的 parser 也會(huì)用到類似的思路。還是有必要掌握的。希望本文能幫大家理清思路。

代碼在 github:https://github.com/QuarkGluonPlasma/tiny-browser

 

責(zé)任編輯:武曉燕 來源: 神光的編程秘籍
相關(guān)推薦

2021-06-04 05:16:33

瀏覽器js源碼

2018-07-31 11:20:26

2012-05-07 14:24:15

HTML 5Web App

2012-05-28 13:09:12

HTML5

2012-04-23 13:43:02

HTML5瀏覽器

2012-03-19 17:25:22

2012-03-20 11:41:18

海豚瀏覽器

2012-03-20 11:31:58

移動(dòng)瀏覽器

2012-03-20 11:07:08

2013-11-20 10:47:57

瀏覽器渲染html

2009-07-29 08:50:10

Windows 7瀏覽器歐洲版

2021-02-06 12:25:42

微軟Chromium瀏覽器

2012-06-21 15:38:02

獵豹瀏覽器

2010-04-05 21:57:14

Netscape瀏覽器

2022-01-24 13:46:24

框架

2012-03-20 11:22:02

QQ手機(jī)瀏覽器

2012-03-19 17:17:00

移動(dòng)瀏覽器歐朋

2012-05-17 09:45:30

2012-03-20 11:35:32

傲游手機(jī)瀏覽器

2013-08-16 17:50:13

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號