• 学习HTMLBeautify.js



    文章截图 - 更好的排版

    源代码下载

    http://jsbeautifier.org/ 这个网站提供了在线的格式化JavaScript的功能,为了学习相关的知识, 我们先从简单的入手。
    下载包中有一个HTML-Beautify.js文件,用来对HTML进行格式化。
    其中的处理考虑了很多情况,如果页面中存在<script>标签,它会自动调用beautify.js来格式化JavaScript,这个文件有15k,428行代码。

    为了达到学习的目的,我对这个JS文件进行了精简,只保留最基本的功能,并且只能处理比较简单的情况,如下一段html代码:
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" ><head><title>Untitled Page</title></head><body><h1>Page Header</h1><ul><li>list item 1</li><li>list item 2</li><li>list item 3</li><li>list item 4</li></ul></body></html>
        
    我们来看下源代码(添加了注释):
        function style_html(html_source, indent_size, indent_character) {
            // Parser可以看做类,multi_parser可以看做是Parser的实例对象
            var Parser, multi_parser;
            function Parser() {
                // 当前字符所在的位置
                this.pos = 0;
                // 当前需要输出的标记的类型(在HTML中只有两种类型 - 内容和标签)
                this.current_mode = 'CONTENT';
                // 记录页面中标签的存储结构,目的是为了在结束标签时设置缩进级别
                // 个人觉得这个地方处理的有点拗口,我们完全可以使用栈来解决
                this.tags = {
                    parent: 'parent1',
                    parentcount: 1,
                    parent1: ''
                };
                // 当前标记的正文和类型
                this.token_text = '';
                this.token_type = '';
                this.Utils = {
                    // 空白字符,这些字符将会被简单的空格取代
                    whitespace: "\n\r\t ".split(''),
                    // 下面这些标签被认为是独立标签
                    single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed'.split(','),
                    // what是否在数组arr中
                    in_array: function(what, arr) {
                        for (var i = 0; i < arr.length; i++) {
                            if (what === arr[i]) {
                                return true;
                            }
                        }
                        return false;
                    }
                }
                // 获取当前正文的标记
                this.get_content = function() {
                    var input_char = '';
                    var content = [];
                    var space = false;
                    // 下一个"<"之前的都是正文
                    while (this.input.charAt(this.pos) !== '<') {
                        if (this.pos >= this.input.length) {
                            return content.length ? content.join('') : ['', 'TK_EOF'];
                        }
                        input_char = this.input.charAt(this.pos);
                        this.pos++;
                        if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
                            if (content.length) {
                                space = true;
                            }
                            continue;
                        }
                        else if (space) {
                            content.push(' ');
                            space = false;
                        }
                        content.push(input_char);
                    }
                    return [content.length ? content.join('') : '', "TK_CONTENT"];
                }
                this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
                    if (this.tags[tag + 'count']) { //check for the existence of this tag type
                        this.tags[tag + 'count']++;
                        this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
                    }
                    else { //otherwise initialize this tag type
                        this.tags[tag + 'count'] = 1;
                        this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
                    }
                    this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
                    this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
                }
                this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
                    if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
                        var temp_parent = this.tags.parent; //check to see if it's a closable tag.
                        while (temp_parent) { //till we reach '' (the initial value);
                            if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
                                break;
                            }
                            temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
                        }
                        if (temp_parent) { //if we caught something
                            this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
                            this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
                        }
                        delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
                        delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
                        if (this.tags[tag + 'count'] == 1) {
                            delete this.tags[tag + 'count'];
                        }
                        else {
                            this.tags[tag + 'count']--;
                        }
                    }
                }
                // 获取当前标签的标记
                this.get_tag = function() {
                    var input_char = '';
                    var content = [];
                    var space = false;
                    do {
                        if (this.pos >= this.input.length) {
                            return content.length ? content.join('') : ['', 'TK_EOF'];
                        }
                        input_char = this.input.charAt(this.pos);
                        this.pos++;
                        if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
                            space = true;
                            continue;
                        }
                        if (input_char === '=') {
                            space = false;
                        }
                        // "="和">"之前不要有空格
                        if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
                            content.push(' ');
                            space = false;
                        }
                        content.push(input_char);
                    } while (input_char !== '>');
                    var tag_complete = content.join('');
                    var tag_index;
                    if (tag_complete.indexOf(' ') != -1) {
                        tag_index = tag_complete.indexOf(' ');
                    }
                    else {
                        tag_index = tag_complete.indexOf('>');
                    }
                    var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
                    var tag_type = "";
                    if (tag_complete.charAt(tag_complete.length - 2) === '/' || this.Utils.in_array(tag_check, this.Utils.single_token)) {
                        tag_type = 'SINGLE';
                    }
                    else {
                        if (tag_check.charAt(0) === '/') {
                            // 结束标签,设置当前indent_level并且将此标签从this.tags中移除,包含此标签的所有子标签
                            this.retrieve_tag(tag_check.substring(1));
                            tag_type = 'END';
                        }
                        else {
                            // 开始标签,将此标签添加到this.tags
                            this.record_tag(tag_check);
                            tag_type = 'START';
                        }
                    }
                    return [content.join(''), "TK_TAG_" + tag_type];
                }
                // 获取下一个标记
                this.get_token = function() {
                    if (this.current_mode === 'CONTENT') {
                        return this.get_content();
                    }
                    if (this.current_mode === 'TAG') {
                        return this.get_tag();
                    }
                }
                this.printer = function(js_source, indent_character, indent_size) {
                    this.input = js_source || '';
                    this.output = [];
                    this.indent_character = indent_character || ' ';
                    this.indent_string = '';
                    this.indent_size = indent_size || 2;
                    this.indent_level = 0;
                    for (var i = 0; i < this.indent_size; i++) {
                        this.indent_string += this.indent_character;
                    }
                    this.print_newline = function(ignore, arr) {
                        this.line_char_count = 0;
                        if (!arr || !arr.length) {
                            return;
                        }
                        if (!ignore) {
                            while (this.Utils.in_array(arr[arr.length - 1], this.Utils.whitespace)) {
                                arr.pop();
                            }
                        }
                        arr.push('\n');
                        for (var i = 0; i < this.indent_level; i++) {
                            arr.push(this.indent_string);
                        }
                    }
                    this.print_token = function(text) {
                        this.output.push(text);
                    }
                    this.indent = function() {
                        this.indent_level++;
                    }
                    this.unindent = function() {
                        if (this.indent_level > 0) {
                            this.indent_level--;
                        }
                    }
                }
                return this;
            }
            /*_____________________--------------------_____________________*/
            // 创建Parser的实例,设置Parser构造函数中的this指向multi_parser对象,同时执行此函数
            multi_parser = new Parser();
            // 调用printer函数,此时printer函数中的this也指向multi_parser对象
            multi_parser.printer(html_source, indent_character, indent_size); //initialize starting values
            // 循环获取每一个标记(Token)直到结束
            while (true) {
                var t = multi_parser.get_token();
                // 当前标记的内容和类型
                multi_parser.token_text = t[0];
                multi_parser.token_type = t[1];
                // 如果这是一个结束标记,跳出循环
                if (multi_parser.token_type === 'TK_EOF') {
                    break;
                }
                switch (multi_parser.token_type) {
                    case 'TK_TAG_START':
                        // 标签开始 - 1.输出新行 2.输出标记内容 3.下一个标记缩进一个单位 4.下一个标记类型为内容
                        multi_parser.print_newline(false, multi_parser.output);
                        multi_parser.print_token(multi_parser.token_text);
                        multi_parser.indent();
                        multi_parser.current_mode = 'CONTENT';
                        break;
                    case 'TK_TAG_END':
                        // 标签结束 - 1.输出新行 2.输出标记内容 3.下一个标记类型为内容
                        multi_parser.print_newline(true, multi_parser.output);
                        multi_parser.print_token(multi_parser.token_text);
                        multi_parser.current_mode = 'CONTENT';
                        break;
                    case 'TK_TAG_SINGLE':
                        // 独立标签(比如<br />) - 1.输出新行 2.输出标记内容 3.下一个标记类型为内容
                        multi_parser.print_newline(false, multi_parser.output);
                        multi_parser.print_token(multi_parser.token_text);
                        multi_parser.current_mode = 'CONTENT';
                        break;
                    case 'TK_CONTENT':
                        // 内容 - 1.输出新行 2.输出标记内容 3.下一个标记类型为标签
                        if (multi_parser.token_text !== '') {
                            multi_parser.print_newline(false, multi_parser.output);
                            multi_parser.print_token(multi_parser.token_text);
                        }
                        multi_parser.current_mode = 'TAG';
                        break;
                }
            }
            // 最终结果
            return multi_parser.output.join('');
        }
    
        $(function() {
            $("#format").click(function() {
                // 格式化id="content"的textarea的内容,缩进为四个空格
                $("#content").val(style_html($("#content").val(), 4, ""));
            });
        });
        
    其中对标签的处理有点拗口,我们特别拿出来讲解。
    比如有如下的结构:
    <body><div><span><span></span></span></div></body>
    获取body标签时,this.tags的内容为:
        {"bodycount":1,"body1":1,"body1parent":"html1"}
        

    获取div时:
        {"bodycount":1,"body1":1,"body1parent":"html1",
        "divcount":1,"div1":2,"div1parent":"body1"}
        

    获取第一个span时:
        {"bodycount":1,"body1":1,"body1parent":"html1",
        "divcount":1,"div1":2,"div1parent":"body1",
        "spancount":1,"span1":3,"span1parent":"div1"}
        

    获取第二个span时:
        {"bodycount":1,"body1":1,"body1parent":"html1",
        "divcount":1,"div1":2,"div1parent":"body1",
        "spancount":2,"span1":3,"span1parent":"div1",
        "span2":4,"span2parent":"span1"}
        

    获取第一个/span时:
        {"bodycount":1,"body1":1,"body1parent":"html1",
        "divcount":1,"div1":2,"div1parent":"body1",
        "spancount":1,"span1":3,"span1parent":"div1"}
        

    此时this.indent_level = 4; 也就是说里面span标签的缩进为4个单位
    获取第二个/span时:
        {"bodycount":1,"body1":1,"body1parent":"html1",
        "divcount":1,"div1":2,"div1parent":"body1"}
        

    此时this.indent_level = 3; 也就是说里面span标签的缩进为3个单位
    以此类推….
  • 相关阅读:
    HDU 2072(字符串的流式操作,学习了)
    HDU 1007 Quoit Design(经典最近点对问题)
    HDU1005 Number Sequence(找规律,周期是变化的)
    HDU 1004 Let the Balloon Rise(map的使用)
    ZCMU 2177 Lucky Numbers (easy)
    2018 HNUCM ACM集训队选拔第一场
    HDU 1162Eddy's picture(MST问题)
    HDU 1142 A Walk Through the Forest(dijkstra+记忆化DFS)
    HDU 1198 Farm Irrigation(并查集,自己构造连通条件或者dfs)
    nyoi 42(欧拉回路)
  • 原文地址:https://www.cnblogs.com/sanshi/p/1529395.html
Copyright © 2020-2023  润新知