手動實現HTML外掛Beautify

lcc發表於2021-09-09

學習HTML-Beautify.js之後,我們發現使用JavaScript對HTML進行解析也並不神秘, 首先是逐字元進行分析,從中提取標記(Token),在HTML只存在兩種型別的標記-標籤和正文, 然後對這些Token進行語法分析,主要是縮排量是多少。
在有這些基本概念後,今天我們就自己來實現這個小程式:

// 最佳化過的HTML-Beautify
    function HtmlBeautify(source, indent_value) {
        this.source = source;
        this.indent_value = indent_value;
        this.result = "";

        this.parse();
    }

    // 分析併產生輸出到this.result
    HtmlBeautify.prototype.parse = function() {
        var that = this;
        // 當前分析到哪個字元,當前標記值,標記型別,
        // 輸出陣列,縮排級別,當前格式化內容(去掉多餘空格)
        var pos = 0, token_value = "", token_type = "",
        output = [], indent_level = 0, is_format_content = true;

        // 把這些標籤作為Single Tag
        var single_token = "br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed".split(',');
        var white_space = "rnt ".split("");

        // 獲取下一個標記(首先獲取正文,如果正文為空則獲取標籤)
        function nextToken() {
            var token_value_array = [], val = "", space = false;

            // "<"之前的所有內容作為正文標籤
            while ((val = that.source[pos]) !== "<") {
                if (pos >= that.source.length) {
                    token_type = "END";
                    return;
                }

                if (is_format_content) {
                    if ($.inArray(val, white_space) >= 0) {
                        space = true;
                        pos++;
                        continue;
                    }

                    if (space) {
                        token_value_array.push(" ");
                        space = false;
                    }
                }
                token_value_array.push(val);
                pos++;
            }

            token_value = token_value_array.join("").replace(/^n+|n$/g,"");
            if ($.trim(token_value) === "") {
                // 如果正文標記為空,則獲取標籤標記
                if(!is_format_content) {
                    is_format_content = true;
                }
                nextTokenTag();
            } else {
                token_type = "CONTENT";
            }
        }

        // 下一個標籤標記
        function nextTokenTag() {
            var token_value_array = [], val = "",
                tagName = "", space = false, is_comment = false;
            
            // 這是一個註釋標籤
            if(that.source[pos + 1] === "!" && 
                that.source[pos + 2] === "-" &&
                that.source[pos + 3] === "-") {
                is_comment = true;
            }

            // 獲取標籤標記,直到遇到">"
            do {
                val = that.source[pos];

                if (!is_comment) {
                    // 如果此字元為空格換行製表符,則跳過此字元
                    if ($.inArray(val, white_space) >= 0) {
                        space = true;
                        pos++;
                        continue;
                    }

                    if (space) {
                        if(token_value_array[token_value_array.length - 1] !== "=" && val !== "=") {
                            token_value_array.push(" ");
                        }
                        space = false;
                    }
                }

                if(val === "/" && that.source[pos + 1] === ">" && token_value_array[token_value_array.length - 1] !== " ") {
                    token_value_array.push(" ");
                }

                token_value_array.push(val);
                pos++;
            } while (val !== ">");

            token_value = $.trim(token_value_array.join(""));
            // 當前標籤的名稱(小寫)
            tagName = getTagName();
            
            if(is_comment) {
                token_type = "SINGLE_TAG";
            } else {
                if (token_value[1] === "/") {
                    // token_value以"</"開始,則認為是結束標籤
                    token_type = "END_TAG";
                } else if ($.inArray(tagName, single_token) >= 0 || token_value[token_value.length - 2] === "/") {
                    // 如果標籤在single_token或者token_value以"/>"結尾,則認為是獨立標籤
                    // 這種判斷沒有考慮這種情況:"<br></br>"
                    token_type = "SINGLE_TAG";
                } else {
                    token_type = "START_TAG";
                    if (tagName === "script" || tagName === "style") {
                        is_format_content = false;
                    }
                }
            }
        }

        function getTagName() {
            var tagName = token_value.substr(1, token_value.length - 2);
            var spaceIndex = tagName.indexOf(" ");
            if (spaceIndex > 0) {
                tagName = tagName.substr(0, spaceIndex);
            }
            return tagName.toLowerCase();
        }

        // 輸出當前標記
        function outputToken() {
            output.push(token_value);
        }
        // 輸出新行
        function outputLine() {
            output.push("n");
        }
        // 輸出縮排
        function outputIndent() {
            for (var i = 0; i < indent_level; i++) {
                output.push(that.indent_value);
            }
        }

        // parse的主體函式,迴圈獲取下一個Token
        while (true) {
            nextToken();

            // 當前Token為結束標記
            if (token_type === "END") {
                break;
            }

            switch (token_type) {
                case "START_TAG":
                    // 我們對縮排的控制非常簡單,開始標籤後縮排一個單位
                    outputLine();
                    outputIndent();
                    outputToken();
                    indent_level++;
                    break;
                case "END_TAG":
                    // 結束標籤前減少一個單位縮排
                    indent_level--;
                    outputLine();
                    outputIndent();
                    outputToken();
                    break;
                case "SINGLE_TAG":
                    outputLine();
                    outputIndent();
                    outputToken();
                    break;
                case "CONTENT":
                    outputLine();
                    if(is_format_content) {
                        outputIndent();
                    }
                    outputToken();
                    break;
            }
        }
        // 去除最前面的"n"
        this.result = output.join("").substr(1);
    };

    $(function() {
        $("#format").click(function() {

            // 例項化HtmlBeautify,傳遞需要解析的HTML片段和縮排字串
            var beautify = new HtmlBeautify($("#content").val(), "    ");
            $("#content").val(beautify.result);

        });
    });

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2331/viewspace-2800840/,如需轉載,請註明出處,否則將追究法律責任。

相關文章