iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
Modern Web

Parser 的深入研究系列 第 14

[Day14] - 簡易 HTML 的 Parse 實作

  • 分享至 

  • xImage
  •  

昨天我們分析 HTML Tokens 要轉換成 AST 需要利用 elementStack 來輔助,今天我們就來實作一下。

複習一下 遇到不同的 token 要做什麼

  1. 如果是 tagStart,則建立一個新的 node A,node A 當作 elementStack 的最後一個元素的 children,並將 node A push elementStack
  2. 如果是 tagEnd,則 pop elementStack 的最後一個元素
  3. 如果是 tagSelfClose 或 Text,則建立一個新的 node A,node A 當作 elementStack 的最後一個元素的 children

根據上面的 3 條規則,做出 4 個 handle function

tagStart handler

// 遇到 tagStart,node A 當作 elementStack 的最後一個元素的 children,
// 並將 node A push elementStack
const handleTagStart = elementStack => token => {
    const node = {type: token.name, children: []}; // node A
    elementStack[elementStack.length - 1].children.push(node);// last children
    elementStack.push(node); // 並將 node A push elementStack
};

tagEnd handler

 // 遇到 tagEnd,則 pop elementStack 的最後一個元素
const handleTagEnd = elementStack => token => {
    elementStack.pop(); // pop elementStack 的最後一個元素
};

tagSelfClose handler

// 遇到 tagSelfClose,新增 node 放到 elementStack 的最後一個元素的 children 中
const handleTagSelfClose = elementStack => token => {
    const node = {type: token.name}; // tagSelfClose node
    elementStack[elementStack.length - 1].children.push(node);// last children
};

Text handler

// 遇到 text,新增 node 放到 elementStack 的最後一個元素的 children 中
const handleText = elementStack => token => {
    const node = {type: 'text', content: token.content}; // text node
    elementStack[elementStack.length - 1].children.push(node);// last children
};

實作

const assert = require('node:assert');

const tokens = [
    {"type": "tagStart", "name": "div"},
    {"type": "tagStart", "name": "p"},
    {"type": "text", "content": "Vue"},
    {"type": "tagEnd", "name": "p"},
    {"type": "tagSelfClose", "name": "input"},
    {"type": "tagStart", "name": "p"},
    {"type": "text", "content": "Template"},
    {"type": "tagEnd", "name": "p"},
    {"type": "tagEnd", "name": "div"},
];

const expected_AST = {
    type: 'root',
    children: [
        {
            "type": "div",
            "children": [
                {
                    "type": "p",
                    "children": [
                        {
                            "type": "text",
                            "content": "Vue"
                        }
                    ]
                },
                {"type": "input"},
                {
                    "type": "p",
                    "children": [
                        {
                            "type": "text",
                            "content": "Template"
                        }
                    ]
                }
            ]
        }
    ]
}

const root = {type: 'root', children: []};
const output_AST = root;
const elementStack = [root];

// 遇到 tagStart,node A 當作 elementStack 的最後一個元素的 children,
// 並將 node A push elementStack
const handleTagStart = elementStack => token => {
    const node = {type: token.name, children: []}; // node A
    elementStack[elementStack.length - 1].children.push(node);// last children
    elementStack.push(node); // 並將 node A push elementStack
};

// 遇到 tagEnd,則 pop elementStack 的最後一個元素
const handleTagEnd = elementStack => token => {
    elementStack.pop(); // pop elementStack 的最後一個元素
};

// 遇到 text,當作 elementStack 的最後一個元素的 children
const handleTagSelfClose = elementStack => token => {
    const node = {type: token.name}; // tagSelfClose node
    elementStack[elementStack.length - 1].children.push(node);// last children
};

// 遇到 tagSelfClose,當作 elementStack 的最後一個元素的 children
const handleText = elementStack => token => {
    const node = {type: 'text', content: token.content}; // text node
    elementStack[elementStack.length - 1].children.push(node);// last children
};

while (tokens.length > 0) {
    // 取出目前要檢查的 token
    const token = tokens.shift();
    if (token.type === 'tagStart') handleTagStart(elementStack)(token);
    if (token.type === 'tagEnd') handleTagEnd(elementStack)(token);
    if (token.type === 'text') handleText(elementStack)(token);
    if (token.type === 'tagSelfClose') handleTagSelfClose(elementStack)(token);
}

assert.deepEqual(output_AST, expected_AST);

相信有許多邦友發現了,最近討論的 HTML 範例都是沒有屬性的,那如果有屬性要如何分析呢?先留給大家思考了,我們下一篇再來討論。

參考資料


上一篇
[Day13] - 簡易 HTML 的 Parse 實作示意圖
下一篇
[Day15] - HTML 加 attr 的狀態圖分析
系列文
Parser 的深入研究32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言