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 的標籤,找到後,截取 後面的字符串,然後循環查找 屬性,直到 找到 > 或 />字符爲止,找到讓後截取後面的字符串。
中間用到了堆棧作爲樹節點作爲串聯。