解析HTML字符串成AST樹

1. 如何將一個字符傳轉換成一個AST樹結構。

直接上代碼:

 

const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
// 匹配 <div
const startTagOpen = new RegExp(`^<${qnameCapture}`)
// 匹配 > />
const startTagClose = /^\s*(\/?)>/
// 匹配 </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i

//注意樹形的html 只能有一個根節點
function parseHtmlToAst(html){

	let text,root,currentParent,stack=[];
	
	while(html){
		let textEnd=html.indexOf("<");
		if(textEnd===0){
			//查找開始tag
			const startTagMatch=parseStartTag();
			if(startTagMatch){
				//生成AST樹
				start(startTagMatch.tagName,startTagMatch.attrs);
				continue;
			}
			//查找結束標籤
			const endTagMatch=html.match(endTag);
			if(endTagMatch){
				advance(endTagMatch[0].length);
				//構造ast樹
				end(endTagMatch[1]);
				continue;
			}
		
		}
		//文本節點
		if(textEnd>0){
			text=html.substring(0,textEnd);
		}
		if(text){
			//截取字符串
			advance(text.length);
			chars(text);
		}
		
		 
	}
	//截些開始標記
	function parseStartTag(){
		const start=html.match(startTagOpen);
		
		let end,attr;
		//找到開始標記
		if(start){
			const match={
				tagName:start[1],
				attrs:[]
			}

			advance(start[0].length)
			//配置屬性
			while(!(end=html.match(startTagClose)) && (attr=html.match(attribute))){
			
				match.attrs.push({
					name:attr[1],
					value: attr[3] || attr[4] || attr[5]
				})
				advance(attr[0].length);
				 
			}
			//匹配結束字符 > 或 />
			if(end){
				advance(end[0].length);
        		return match;
			}
		}
		
	}
	
	//截取字符串
	function advance (n) {
    		html = html.substring(n)
  }
	//構造AST樹形
	function start(tagName,attrs){
		const element=createAstElement(tagName,attrs);
		
		if(!root){
			root=element;
		}
		currentParent=element;
		stack.push(element);
	}
	
	//結束鉤爪樹形
	function end(tagName){
		const element=stack.pop();
		currentParent=stack[stack.length-1];
		if(currentParent){
			element.parent=currentParent;
			currentParent.children.push(element);
		}

	}
	//處理文本節點
	function chars(text){
		text=text.trim();
		if(text.length>0){
			currentParent.children.push({
				type:3,
				text
			})
		}
		 
	}
	
	
	function createAstElement(tagName,attrs){
		return {
			tag: tagName,
			type: 1,
			children:[],
			attrs,
			parent
		
		}
	}
	return root;

}

let html=`<div id="app" style="color:red;width:200px">
	你好:{{name}}
	<span class="text" style="color:green">{{age}}
	</span>
	</div>
`;


let root=parseHtmlToAst(html);
console.info(root)



代碼的邏輯是:
對字符串的處理,從頭開始處理,先找 < 開頭的字符,如果找到則用 正則表達式查找 <div 的標籤,找到後,截取 後面的字符串,然後循環查找 屬性,直到 找到 > 或 />字符爲止,找到讓後截取後面的字符串。

中間用到了堆棧作爲樹節點作爲串聯。

2.構造結果

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章