diff --git a/README.md b/README.md index a86e2dfbb..22ff459ef 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,7 @@ HTML Beautifier Options: -w, --wrap-line-length Maximum characters per line (0 disables) [250] -A, --wrap-attributes Wrap attributes to new lines [auto|force|force-aligned|force-expand-multiline] ["auto"] -i, --wrap-attributes-indent-size Indent wrapped attributes to after N characters [indent-size] (ignored if wrap-attributes is "force-aligned") + -d, --inline List of tags to be considered inline tags -U, --unformatted List of tags (defaults to inline) that should not be reformatted -T, --content_unformatted List of tags (defaults to pre) whose content should not be reformatted -E, --extra_liners List of tags (defaults to [head,body,/html] that should have an extra newline before them. diff --git a/js/lib/beautify-html.js b/js/lib/beautify-html.js index ff66cf7df..c90e55f35 100644 --- a/js/lib/beautify-html.js +++ b/js/lib/beautify-html.js @@ -47,8 +47,9 @@ wrap_line_length (default 250) - maximum amount of characters per line (0 = disable) brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none" put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are. + inline (defaults to inline tags) - list of tags to be considered inline tags unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted - content_unformatted (defaults to pre tag) - list of tags, whose content shouldn't be reformatted + content_unformatted (defaults to ["pre", "textarea"] tags) - list of tags, whose content shouldn't be reformatted indent_scripts (default normal) - "keep"|"separate"|"normal" preserve_newlines (default true) - whether existing line breaks before elements should be preserved Only works before elements, not inside tags or for text. @@ -206,6 +207,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { indent_character, wrap_line_length, brace_style, + inline_tags, unformatted, content_unformatted, preserve_newlines, @@ -237,7 +239,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char; brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style; wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10); - unformatted = options.unformatted || [ + inline_tags = options.inline || [ // https://www.w3.org/TR/html5/dom.html#phrasing-content 'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', 'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', @@ -248,8 +250,9 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { // prexisting - not sure of full effect of removing, leaving in 'acronym', 'address', 'big', 'dt', 'ins', 'strike', 'tt', ]; + unformatted = options.unformatted || []; content_unformatted = options.content_unformatted || [ - 'pre', + 'pre', 'textarea' ]; preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines; max_preserve_newlines = preserve_newlines ? @@ -294,8 +297,11 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { parentcount: 1, parent1: '' }; - this.tag_type = ''; - this.token_text = this.last_token = this.last_text = this.token_type = ''; + this.last_token = { + text: '', + type: '' + }; + this.token_text = ''; this.newlines = 0; this.indent_content = indent_inner_html; this.indent_body_inner_html = indent_body_inner_html; @@ -330,7 +336,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { } } return false; - } + }, }; // Return true if the given text is composed entirely of whitespace. @@ -379,12 +385,19 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.get_content = function() { //function to capture regular content between tags var input_char = '', + token = { + text: '', + type: 'TK_CONTENT' + }, content = [], handlebarsStarted = 0; while (this.input.charAt(this.pos) !== '<' || handlebarsStarted === 2) { if (this.pos >= this.input.length) { - return content.length ? content.join('') : ['', 'TK_EOF']; + if (!content.length) { + token.type = 'TK_EOF'; + } + break; } if (handlebarsStarted < 2 && this.traverse_whitespace()) { @@ -415,9 +428,11 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { // These are tags and not content. break; } else if (peek3 === '{{!') { - return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT']; + token = this.get_tag(); + token.type = 'TK_TAG_HANDLEBARS_COMMENT'; + return token; } else if (this.input.substr(this.pos, 2) === '{{') { - if (this.get_tag(true) === '{{else}}') { + if (this.get_tag(true).text === '{{else}}') { break; } } @@ -427,12 +442,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.line_char_count++; content.push(input_char); //letter at-a-time (or string) inserted to an array } - return content.length ? content.join('') : ''; + token.text = content.join(''); + return token; }; this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify if (this.pos === this.input.length) { - return ['', 'TK_EOF']; + return {text: '', type: 'TK_EOF'}; } var content = ''; var reg_match = new RegExp('', 'igm'); @@ -443,7 +459,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { content = this.input.substring(this.pos, end_script); this.pos = end_script; } - return content; + return {text: content, type: 'TK_' + name}; }; this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object @@ -500,6 +516,15 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.get_tag = function(peek) { //function to get a full tag and parse its type var input_char = '', + token = { + text: '', + type: '', + tag_type: '', + tag_name: '', + is_inline_tag: false, + is_opening_tag: false, + is_closing_tag: false + }, content = [], comment = '', space = false, @@ -520,7 +545,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.pos = orig_pos; this.line_char_count = orig_line_char_count; } - return content.length ? content.join('') : ['', 'TK_EOF']; + if (content.length) { + token.text = content.join(''); + } else { + token.type = 'TK_EOF'; + } + + return token; } input_char = this.input.charAt(this.pos); @@ -659,56 +690,65 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2; } var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase(); + token.is_closing_tag = tag_check.charAt(0) === '/'; + token.tag_name = token.is_closing_tag ? tag_check.substr(1) : tag_check; + token.is_inline_tag = this.Utils.in_array(token.tag_name, inline_tags); + + if (tag_complete.charAt(tag_complete.length - 2) === '/' || this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) - if (!peek) { - this.tag_type = 'SINGLE'; - } + token.tag_type = 'SINGLE'; + token.is_closing_tag = true; } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') { if (!peek) { this.indent_to_tag('if'); - this.tag_type = 'HANDLEBARS_ELSE'; + token.tag_type = 'HANDLEBARS_ELSE'; this.indent_content = true; this.traverse_whitespace(); } - } else if (this.is_unformatted(tag_check, unformatted) || - this.is_unformatted(tag_check, content_unformatted)) { + } else if (this.Utils.in_array(tag_check, unformatted) || + this.Utils.in_array(tag_check, content_unformatted)) { // do not reformat the "unformatted" or "content_unformatted" tags + if (this.Utils.in_array(tag_check, unformatted)) { + content = [this.input.slice(tag_start, this.pos)]; + } comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function content.push(comment); tag_end = this.pos - 1; - this.tag_type = 'SINGLE'; + token.tag_type = 'SINGLE'; + token.is_closing_tag = true; } else if (tag_check === 'script' && (tag_complete.search('type') === -1 || (tag_complete.search('type') > -1 && tag_complete.search(/\b(text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect)/) > -1))) { if (!peek) { this.record_tag(tag_check); - this.tag_type = 'SCRIPT'; + token.tag_type = 'SCRIPT'; } } else if (tag_check === 'style' && (tag_complete.search('type') === -1 || (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) { if (!peek) { this.record_tag(tag_check); - this.tag_type = 'STYLE'; + token.tag_type = 'STYLE'; } } else if (tag_check.charAt(0) === '!') { //peek for 10; if (comment.indexOf(''; matched = true; @@ -859,34 +903,16 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { }; this.get_token = function() { //initial handler for token-retrieval - var token; - - if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript - var type = this.last_token.substr(7); + var token ; + if (this.last_token.type === 'TK_TAG_SCRIPT' || this.last_token.type === 'TK_TAG_STYLE') { //check if we need to format javascript + var type = this.last_token.type.substr(7); token = this.get_contents_to(type); - if (typeof token !== 'string') { - return token; - } - return [token, 'TK_' + type]; - } - if (this.current_mode === 'CONTENT') { + } else if (this.current_mode === 'CONTENT') { token = this.get_content(); - if (typeof token !== 'string') { - return token; - } else { - return [token, 'TK_CONTENT']; - } - } - - if (this.current_mode === 'TAG') { + } else if (this.current_mode === 'TAG') { token = this.get_tag(); - if (typeof token !== 'string') { - return token; - } else { - var tag_name_type = 'TK_TAG_' + this.tag_type; - return [token, tag_name_type]; - } } + return token; }; this.get_full_indent = function(level) { @@ -898,38 +924,6 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { return Array(level + 1).join(this.indent_string); }; - this.is_unformatted = function(tag_check, unformatted) { - //is this an HTML5 block-level link? - if (!this.Utils.in_array(tag_check, unformatted)) { - return false; - } - - if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) { - return true; - } - - //at this point we have an tag; is its first child something we want to remain - //unformatted? - var next_tag = this.get_tag(true /* peek. */ ); - - next_tag = next_tag || ''; - if (typeof next_tag !== 'string') { - next_tag = next_tag[0]; - } - - // test next_tag to see if it is just html tag (no external content) - var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/); - - // if next_tag comes back but is not an isolated tag, then - // let's treat the 'a' tag as having content - // and respect the unformatted option - if (!tag || this.Utils.in_array(tag[1], unformatted)) { - return true; - } else { - return false; - } - }; - this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions this.input = js_source || ''; //gets the input for the Parser @@ -1025,59 +1019,77 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.beautify = function() { multi_parser = new Parser(); //wrapping functions Parser multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values + var token = null; + var last_tag_token = { + text: '', + type: '', + tag_name: '', + is_opening_tag: false, + is_closing_tag: false, + is_inline_tag: false + }; while (true) { - var t = multi_parser.get_token(); - multi_parser.token_text = t[0]; - multi_parser.token_type = t[1]; + token = multi_parser.get_token(); - if (multi_parser.token_type === 'TK_EOF') { + if (token.type === 'TK_EOF') { break; } - switch (multi_parser.token_type) { + switch (token.type) { case 'TK_TAG_START': - multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); + if (!last_tag_token.is_inline_tag && !token.is_inline_tag) { + multi_parser.print_newline(false, multi_parser.output); + } + multi_parser.print_token(token.text); if (multi_parser.indent_content) { - if ((multi_parser.indent_body_inner_html || !multi_parser.token_text.match(//)) && - (multi_parser.indent_head_inner_html || !multi_parser.token_text.match(//))) { + if ((multi_parser.indent_body_inner_html || token.tag_name !== 'body') && + (multi_parser.indent_head_inner_html || token.tag_name !== 'head')) { multi_parser.indent(); } multi_parser.indent_content = false; } + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_STYLE': case 'TK_TAG_SCRIPT': multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_END': - //Print new line only if the tag has no content and has child - if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') { - var tag_name = (multi_parser.token_text.match(/\w+/) || [])[0]; - var tag_extracted_from_last_output = null; - if (multi_parser.output.length) { - tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/); - } - if (tag_extracted_from_last_output === null || - (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) { + if (!token.is_inline_tag) { + //Print new line only if the tag has no content and has child + if ( + !( + !last_tag_token.is_closing_tag && + token.tag_name === last_tag_token.tag_name && + !multi_parser.Utils.in_array(token.tag_name, content_unformatted) + ) && + !last_tag_token.is_inline_tag + ) { multi_parser.print_newline(false, multi_parser.output); } } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_SINGLE': // Don't add a newline before elements that should remain unformatted. - var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i); - if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) { + var tag_check = token.text.match(/^\s*<([a-z-]+)/i); + if ( + !tag_check || + !multi_parser.Utils.in_array(tag_check[1], inline_tags) && + !multi_parser.Utils.in_array(tag_check[1], unformatted) + ) { multi_parser.print_newline(false, multi_parser.output); } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_HANDLEBARS_ELSE': @@ -1096,7 +1108,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { if (!foundIfOnCurrentLine) { multi_parser.print_newline(false, multi_parser.output); } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); if (multi_parser.indent_content) { multi_parser.indent(); multi_parser.indent_content = false; @@ -1104,23 +1116,26 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_HANDLEBARS_COMMENT': - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); multi_parser.current_mode = 'TAG'; break; case 'TK_CONTENT': - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); multi_parser.current_mode = 'TAG'; + if (!token.text) { + continue; + } break; case 'TK_STYLE': case 'TK_SCRIPT': - if (multi_parser.token_text !== '') { + if (token.text !== '') { multi_parser.print_newline(false, multi_parser.output); - var text = multi_parser.token_text, + var text = token.text, _beautifier, script_indent_level = 1; - if (multi_parser.token_type === 'TK_SCRIPT') { + if (token.type === 'TK_SCRIPT') { _beautifier = typeof js_beautify === 'function' && js_beautify; - } else if (multi_parser.token_type === 'TK_STYLE') { + } else if (token.type === 'TK_STYLE') { _beautifier = typeof css_beautify === 'function' && css_beautify; } @@ -1159,13 +1174,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { default: // We should not be getting here but we don't want to drop input on the floor // Just output the text and move on - if (multi_parser.token_text !== '') { - multi_parser.print_token(multi_parser.token_text); + if (token.text !== '') { + multi_parser.print_token(token.text); } break; } - multi_parser.last_token = multi_parser.token_type; - multi_parser.last_text = multi_parser.token_text; + multi_parser.last_token = token; + } var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, ''); diff --git a/js/lib/cli.js b/js/lib/cli.js index 99b243602..977209605 100755 --- a/js/lib/cli.js +++ b/js/lib/cli.js @@ -95,6 +95,7 @@ var path = require('path'), "space_around_selector_separator": Boolean, // HTML-only "max_char": Number, // obsolete since 1.3.5 + "inline": [String, Array], "unformatted": [String, Array], "content_unformatted": [String, Array], "indent_inner_html": [Boolean], @@ -144,6 +145,7 @@ var path = require('path'), "A": ["--wrap_attributes"], "i": ["--wrap_attributes_indent_size"], "W": ["--max_char"], // obsolete since 1.3.5 + "d": ["--inline"], "U": ["--unformatted"], "T": ["--content_unformatted"], "I": ["--indent_inner_html"], diff --git a/js/src/html/beautifier.js b/js/src/html/beautifier.js index c8732bd53..3be7a614d 100644 --- a/js/src/html/beautifier.js +++ b/js/src/html/beautifier.js @@ -58,6 +58,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { indent_character, wrap_line_length, brace_style, + inline_tags, unformatted, content_unformatted, preserve_newlines, @@ -89,7 +90,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char; brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style; wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10); - unformatted = options.unformatted || [ + inline_tags = options.inline || [ // https://www.w3.org/TR/html5/dom.html#phrasing-content 'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', 'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', @@ -100,8 +101,9 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { // prexisting - not sure of full effect of removing, leaving in 'acronym', 'address', 'big', 'dt', 'ins', 'strike', 'tt', ]; + unformatted = options.unformatted || []; content_unformatted = options.content_unformatted || [ - 'pre', + 'pre', 'textarea' ]; preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines; max_preserve_newlines = preserve_newlines ? @@ -146,8 +148,11 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { parentcount: 1, parent1: '' }; - this.tag_type = ''; - this.token_text = this.last_token = this.last_text = this.token_type = ''; + this.last_token = { + text: '', + type: '' + }; + this.token_text = ''; this.newlines = 0; this.indent_content = indent_inner_html; this.indent_body_inner_html = indent_body_inner_html; @@ -182,7 +187,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { } } return false; - } + }, }; // Return true if the given text is composed entirely of whitespace. @@ -231,12 +236,19 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.get_content = function() { //function to capture regular content between tags var input_char = '', + token = { + text: '', + type: 'TK_CONTENT' + }, content = [], handlebarsStarted = 0; while (this.input.charAt(this.pos) !== '<' || handlebarsStarted === 2) { if (this.pos >= this.input.length) { - return content.length ? content.join('') : ['', 'TK_EOF']; + if (!content.length) { + token.type = 'TK_EOF'; + } + break; } if (handlebarsStarted < 2 && this.traverse_whitespace()) { @@ -267,9 +279,11 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { // These are tags and not content. break; } else if (peek3 === '{{!') { - return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT']; + token = this.get_tag(); + token.type = 'TK_TAG_HANDLEBARS_COMMENT'; + return token; } else if (this.input.substr(this.pos, 2) === '{{') { - if (this.get_tag(true) === '{{else}}') { + if (this.get_tag(true).text === '{{else}}') { break; } } @@ -279,12 +293,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.line_char_count++; content.push(input_char); //letter at-a-time (or string) inserted to an array } - return content.length ? content.join('') : ''; + token.text = content.join(''); + return token; }; this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify if (this.pos === this.input.length) { - return ['', 'TK_EOF']; + return {text: '', type: 'TK_EOF'}; } var content = ''; var reg_match = new RegExp('', 'igm'); @@ -295,7 +310,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { content = this.input.substring(this.pos, end_script); this.pos = end_script; } - return content; + return {text: content, type: 'TK_' + name}; }; this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object @@ -352,6 +367,15 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.get_tag = function(peek) { //function to get a full tag and parse its type var input_char = '', + token = { + text: '', + type: '', + tag_type: '', + tag_name: '', + is_inline_tag: false, + is_opening_tag: false, + is_closing_tag: false + }, content = [], comment = '', space = false, @@ -372,7 +396,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.pos = orig_pos; this.line_char_count = orig_line_char_count; } - return content.length ? content.join('') : ['', 'TK_EOF']; + if (content.length) { + token.text = content.join(''); + } else { + token.type = 'TK_EOF'; + } + + return token; } input_char = this.input.charAt(this.pos); @@ -511,56 +541,65 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2; } var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase(); + token.is_closing_tag = tag_check.charAt(0) === '/'; + token.tag_name = token.is_closing_tag ? tag_check.substr(1) : tag_check; + token.is_inline_tag = this.Utils.in_array(token.tag_name, inline_tags); + + if (tag_complete.charAt(tag_complete.length - 2) === '/' || this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) - if (!peek) { - this.tag_type = 'SINGLE'; - } + token.tag_type = 'SINGLE'; + token.is_closing_tag = true; } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') { if (!peek) { this.indent_to_tag('if'); - this.tag_type = 'HANDLEBARS_ELSE'; + token.tag_type = 'HANDLEBARS_ELSE'; this.indent_content = true; this.traverse_whitespace(); } - } else if (this.is_unformatted(tag_check, unformatted) || - this.is_unformatted(tag_check, content_unformatted)) { + } else if (this.Utils.in_array(tag_check, unformatted) || + this.Utils.in_array(tag_check, content_unformatted)) { // do not reformat the "unformatted" or "content_unformatted" tags + if (this.Utils.in_array(tag_check, unformatted)) { + content = [this.input.slice(tag_start, this.pos)]; + } comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function content.push(comment); tag_end = this.pos - 1; - this.tag_type = 'SINGLE'; + token.tag_type = 'SINGLE'; + token.is_closing_tag = true; } else if (tag_check === 'script' && (tag_complete.search('type') === -1 || (tag_complete.search('type') > -1 && tag_complete.search(/\b(text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect)/) > -1))) { if (!peek) { this.record_tag(tag_check); - this.tag_type = 'SCRIPT'; + token.tag_type = 'SCRIPT'; } } else if (tag_check === 'style' && (tag_complete.search('type') === -1 || (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) { if (!peek) { this.record_tag(tag_check); - this.tag_type = 'STYLE'; + token.tag_type = 'STYLE'; } } else if (tag_check.charAt(0) === '!') { //peek for 10; if (comment.indexOf(''; matched = true; @@ -711,34 +754,16 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { }; this.get_token = function() { //initial handler for token-retrieval - var token; - - if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript - var type = this.last_token.substr(7); + var token ; + if (this.last_token.type === 'TK_TAG_SCRIPT' || this.last_token.type === 'TK_TAG_STYLE') { //check if we need to format javascript + var type = this.last_token.type.substr(7); token = this.get_contents_to(type); - if (typeof token !== 'string') { - return token; - } - return [token, 'TK_' + type]; - } - if (this.current_mode === 'CONTENT') { + } else if (this.current_mode === 'CONTENT') { token = this.get_content(); - if (typeof token !== 'string') { - return token; - } else { - return [token, 'TK_CONTENT']; - } - } - - if (this.current_mode === 'TAG') { + } else if (this.current_mode === 'TAG') { token = this.get_tag(); - if (typeof token !== 'string') { - return token; - } else { - var tag_name_type = 'TK_TAG_' + this.tag_type; - return [token, tag_name_type]; - } } + return token; }; this.get_full_indent = function(level) { @@ -750,38 +775,6 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { return Array(level + 1).join(this.indent_string); }; - this.is_unformatted = function(tag_check, unformatted) { - //is this an HTML5 block-level link? - if (!this.Utils.in_array(tag_check, unformatted)) { - return false; - } - - if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) { - return true; - } - - //at this point we have an tag; is its first child something we want to remain - //unformatted? - var next_tag = this.get_tag(true /* peek. */ ); - - next_tag = next_tag || ''; - if (typeof next_tag !== 'string') { - next_tag = next_tag[0]; - } - - // test next_tag to see if it is just html tag (no external content) - var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/); - - // if next_tag comes back but is not an isolated tag, then - // let's treat the 'a' tag as having content - // and respect the unformatted option - if (!tag || this.Utils.in_array(tag[1], unformatted)) { - return true; - } else { - return false; - } - }; - this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions this.input = js_source || ''; //gets the input for the Parser @@ -877,59 +870,77 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { this.beautify = function() { multi_parser = new Parser(); //wrapping functions Parser multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values + var token = null; + var last_tag_token = { + text: '', + type: '', + tag_name: '', + is_opening_tag: false, + is_closing_tag: false, + is_inline_tag: false + }; while (true) { - var t = multi_parser.get_token(); - multi_parser.token_text = t[0]; - multi_parser.token_type = t[1]; + token = multi_parser.get_token(); - if (multi_parser.token_type === 'TK_EOF') { + if (token.type === 'TK_EOF') { break; } - switch (multi_parser.token_type) { + switch (token.type) { case 'TK_TAG_START': - multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); + if (!last_tag_token.is_inline_tag && !token.is_inline_tag) { + multi_parser.print_newline(false, multi_parser.output); + } + multi_parser.print_token(token.text); if (multi_parser.indent_content) { - if ((multi_parser.indent_body_inner_html || !multi_parser.token_text.match(//)) && - (multi_parser.indent_head_inner_html || !multi_parser.token_text.match(//))) { + if ((multi_parser.indent_body_inner_html || token.tag_name !== 'body') && + (multi_parser.indent_head_inner_html || token.tag_name !== 'head')) { multi_parser.indent(); } multi_parser.indent_content = false; } + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_STYLE': case 'TK_TAG_SCRIPT': multi_parser.print_newline(false, multi_parser.output); - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_END': - //Print new line only if the tag has no content and has child - if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') { - var tag_name = (multi_parser.token_text.match(/\w+/) || [])[0]; - var tag_extracted_from_last_output = null; - if (multi_parser.output.length) { - tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/); - } - if (tag_extracted_from_last_output === null || - (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) { + if (!token.is_inline_tag) { + //Print new line only if the tag has no content and has child + if ( + !( + !last_tag_token.is_closing_tag && + token.tag_name === last_tag_token.tag_name && + !multi_parser.Utils.in_array(token.tag_name, content_unformatted) + ) && + !last_tag_token.is_inline_tag + ) { multi_parser.print_newline(false, multi_parser.output); } } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_SINGLE': // Don't add a newline before elements that should remain unformatted. - var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i); - if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) { + var tag_check = token.text.match(/^\s*<([a-z-]+)/i); + if ( + !tag_check || + !multi_parser.Utils.in_array(tag_check[1], inline_tags) && + !multi_parser.Utils.in_array(tag_check[1], unformatted) + ) { multi_parser.print_newline(false, multi_parser.output); } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); + last_tag_token = token; multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_HANDLEBARS_ELSE': @@ -948,7 +959,7 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { if (!foundIfOnCurrentLine) { multi_parser.print_newline(false, multi_parser.output); } - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); if (multi_parser.indent_content) { multi_parser.indent(); multi_parser.indent_content = false; @@ -956,23 +967,26 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_HANDLEBARS_COMMENT': - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); multi_parser.current_mode = 'TAG'; break; case 'TK_CONTENT': - multi_parser.print_token(multi_parser.token_text); + multi_parser.print_token(token.text); multi_parser.current_mode = 'TAG'; + if (!token.text) { + continue; + } break; case 'TK_STYLE': case 'TK_SCRIPT': - if (multi_parser.token_text !== '') { + if (token.text !== '') { multi_parser.print_newline(false, multi_parser.output); - var text = multi_parser.token_text, + var text = token.text, _beautifier, script_indent_level = 1; - if (multi_parser.token_type === 'TK_SCRIPT') { + if (token.type === 'TK_SCRIPT') { _beautifier = typeof js_beautify === 'function' && js_beautify; - } else if (multi_parser.token_type === 'TK_STYLE') { + } else if (token.type === 'TK_STYLE') { _beautifier = typeof css_beautify === 'function' && css_beautify; } @@ -1011,13 +1025,13 @@ function Beautifier(html_source, options, js_beautify, css_beautify) { default: // We should not be getting here but we don't want to drop input on the floor // Just output the text and move on - if (multi_parser.token_text !== '') { - multi_parser.print_token(multi_parser.token_text); + if (token.text !== '') { + multi_parser.print_token(token.text); } break; } - multi_parser.last_token = multi_parser.token_type; - multi_parser.last_text = multi_parser.token_text; + multi_parser.last_token = token; + } var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, ''); diff --git a/js/test/generated/beautify-html-tests.js b/js/test/generated/beautify-html-tests.js index ef9e4ec65..afd151223 100644 --- a/js/test/generated/beautify-html-tests.js +++ b/js/test/generated/beautify-html-tests.js @@ -2862,6 +2862,51 @@ function run_html_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_be ''); + //============================================================ + // Inline tags formatting + reset_options(); + test_fragment('
'); + test_fragment( + '
Nested spans
', + // -- output -- + '
\n' + + '
Nested spans
\n' + + '
'); + test_fragment( + '

Should remove attribute newlines

', + // -- output -- + '

Should remove attribute newlines

'); + test_fragment('
All on one line
'); + test_fragment('{{content}}'); + test_fragment('{{#if 1}}{{content}}{{/if}}'); + + + //============================================================ + // unformatted to prevent formatting changes + reset_options(); + opts.unformatted = ['u']; + test_fragment('
Ignore block tags in unformatted regions
'); + test_fragment('
Don\'t wrap unformatted regions with extra newlines
'); + test_fragment( + ' \n' + + '\n' + + '\n' + + ' Ignore extra whitespace \n' + + '\n' + + '\n' + + ' '); + test_fragment( + '
Ignore whitespace in attributes
'); + test_fragment( + 'Ignore whitespace in attributes'); + + //============================================================ // content_unformatted to prevent formatting content reset_options(); @@ -2878,25 +2923,25 @@ function run_html_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_be '\n' + ''); test_fragment( - '

Beautify me

But not me

', + '

Beautify me

But not me

', // -- output -- '
\n' + '

Beautify me

\n' + '
\n' + - '

But not me

'); + '

But not me

'); test_fragment( '
Beautify me

Beautify me

But not me

', + '>But not me

', // -- output -- '
\n' + '

Beautify me

\n' + '
\n' + - '

But not me

'); + '>But not me

'); test_fragment('
blabla
something here
'); test_fragment('

'); test_fragment( @@ -3116,8 +3161,8 @@ function run_html_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_be '
content content
'); bth('Text Link Text'); - var unformatted = opts.unformatted; - opts.unformatted = ['script', 'style']; + var content_unformatted = opts.content_unformatted; + opts.content_unformatted = ['script', 'style']; bth('