成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手寫簡易瀏覽器之Html Parser 篇

系統 瀏覽器
這篇是簡易瀏覽器中 html parser 的實現,少了自閉合標簽的處理,就是差一個 if else,后面會補上。

 [[403967]]

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

思路分析

實現 html parser 主要分為詞法分析和語法分析兩步。

詞法分析

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

  • 開始標簽,如 <div>
  • 結束標簽,如 </div>
  • 注釋標簽,如 <!--comment-->
  • doctype 標簽,如 <!doctype html>
  • text,如 aaa

這是最外層的 token,開始標簽內部還要分出屬性,如 id="aaa" 這種。

也就是有這幾種情況:

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

語法分析

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

html 的 ast 的組裝主要是考慮父子關系,記錄當前的 parent,然后 text、children 都設置到當前 parent 上。

我們來用代碼實現一下:

代碼實現

詞法分析

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

正則

結束標簽就是

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

注釋標簽是 中間夾著非 --> 字符出現任意次

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

doctype 標簽是 字符出現多次,加 >

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

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

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

開始標簽是 < 開頭,接 a-zA-Z0-9 和 - 出現多次,然后是屬性的正則,最后是 > 結尾

  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 和結束標簽、開始標簽:

  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

經過詞法分析,我們能拿到所有的 token:

語法分析

token 拆分之后,我們需要再把這些 token 組裝在一起,只處理 startTag、endTag 和 text 節點。通過 currentParent 記錄當前 tag。

  • startTag 創建 AST,掛到 currentParent 的 children 上,然后 currentParent 變成新創建的 tag
  • endTag 的時候把 currentParent 設置為當前 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。

總結

這篇是簡易瀏覽器中 html parser 的實現,少了自閉合標簽的處理,就是差一個 if else,后面會補上。

我們分析了思路并進行了實現:通過正則來進行 token 的拆分,把拆出的 token 通過回調函數暴露出去,之后進行 AST 的組裝,需要記錄當前的 parent,來生成父子關系正確的 AST。

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

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

 

責任編輯:武曉燕 來源: 神光的編程秘籍
相關推薦

2021-06-04 05:16:33

瀏覽器js源碼

2018-07-31 11:20:26

2012-05-28 13:09:12

HTML5

2012-05-07 14:24:15

HTML 5Web App

2012-04-23 13:43:02

HTML5瀏覽器

2012-03-20 11:07:08

2012-03-20 11:41:18

海豚瀏覽器

2012-03-20 11:31:58

移動瀏覽器

2012-03-19 17:25:22

2009-07-29 08:50:10

Windows 7瀏覽器歐洲版

2013-11-20 10:47:57

瀏覽器渲染html

2012-03-19 17:17:00

移動瀏覽器歐朋

2012-03-20 11:22:02

QQ手機瀏覽器

2012-06-21 15:38:02

獵豹瀏覽器

2010-04-05 21:57:14

Netscape瀏覽器

2021-02-06 12:25:42

微軟Chromium瀏覽器

2022-01-24 13:46:24

框架

2012-03-20 11:35:32

傲游手機瀏覽器

2012-05-17 09:45:30

2013-08-16 17:50:13

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲欧美在线视频 | 91久久爽久久爽爽久久片 | 日韩精品成人免费观看视频 | 国产精品久久久久久久久久久久久久 | 日韩av在线免费 | 无吗视频| 91精品国产综合久久久久久蜜臀 | 日韩欧美一级 | 国产精品视频免费观看 | 日韩在线免费视频 | 香蕉视频1024 | 亚洲欧美综合精品久久成人 | 综合一区二区三区 | 欧美在线视频一区 | 国产特级毛片aaaaaa | 国产在线观看不卡一区二区三区 | 99久久婷婷国产综合精品电影 | 第一区在线观看免费国语入口 | 国产精品一二区 | 成人黄色在线 | 久久久久久国产精品 | 在线成人免费视频 | 日本不卡免费新一二三区 | 在线观看视频福利 | 久久久夜夜夜 | 国产一区二区三区欧美 | 国产精品一区二区三区在线 | 亚州成人 | 殴美成人在线视频 | 91视频在线| 91精品久久久久久久久 | 91精品国产麻豆 | 欧美日韩亚洲一区 | 国产成人免费观看 | 欧美日韩成人在线 | 国产精品我不卡 | 久久精品网 | 综合精品 | 一片毛片 | 99热在线免费 | 久久精品一二三影院 |