diff --git a/README.md b/README.md
index 4d2808657..d5b8a8d88 100644
--- a/README.md
+++ b/README.md
@@ -216,7 +216,7 @@ HTML Beautifier Options:
-b, --brace-style [collapse-preserve-inline|collapse|expand|end-expand|none] ["collapse"]
-S, --indent-scripts [keep|separate|normal] ["normal"]
-w, --wrap-line-length Maximum characters per line (0 disables) [250]
- -A, --wrap-attributes Wrap attributes to new lines [auto|force|force-aligned] ["auto"]
+ -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")
-U, --unformatted List of tags (defaults to inline) that 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 c21b813e7..ab02c44b2 100644
--- a/js/lib/beautify-html.js
+++ b/js/lib/beautify-html.js
@@ -102,6 +102,8 @@
wrap_attributes,
wrap_attributes_indent_size,
is_wrap_attributes_force,
+ is_wrap_attributes_force_expand_multiline,
+ is_wrap_attributes_force_aligned,
end_with_newline,
extra_liners,
eol;
@@ -141,6 +143,8 @@
wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes;
wrap_attributes_indent_size = (isNaN(parseInt(options.wrap_attributes_indent_size, 10))) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10);
is_wrap_attributes_force = wrap_attributes.substr(0, 'force'.length) === 'force';
+ is_wrap_attributes_force_expand_multiline = (wrap_attributes === 'force-expand-multiline');
+ is_wrap_attributes_force_aligned = (wrap_attributes === 'force-aligned');
end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
extra_liners = (typeof options.extra_liners === 'object') && options.extra_liners ?
options.extra_liners.concat() : (typeof options.extra_liners === 'string') ?
@@ -361,10 +365,13 @@
comment = '',
space = false,
first_attr = true,
+ has_wrapped_attrs = false,
tag_start, tag_end,
tag_start_char,
orig_pos = this.pos,
- orig_line_char_count = this.line_char_count;
+ orig_line_char_count = this.line_char_count,
+ is_tag_closed = false,
+ tail;
peek = peek !== undefined ? peek : false;
@@ -388,27 +395,44 @@
if (input_char === "'" || input_char === '"') {
input_char += this.get_unformatted(input_char);
space = true;
-
}
if (input_char === '=') { //no space before =
space = false;
}
-
+ tail = this.input.substr(this.pos - 1);
+ if (is_wrap_attributes_force_expand_multiline && has_wrapped_attrs && !is_tag_closed && (input_char === '>' || input_char === '/')) {
+ if (tail.match(/^\/?\s*>/)) {
+ space = false;
+ is_tag_closed = true;
+ this.print_newline(false, content);
+ this.print_indentation(content);
+ }
+ }
if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
//no space after = or before >
var wrapped = this.space_or_wrap(content);
var indentAttrs = wrapped && input_char !== '/' && !is_wrap_attributes_force;
space = false;
- if (!first_attr && is_wrap_attributes_force && input_char !== '/') {
- this.print_newline(false, content);
- this.print_indentation(content);
- indentAttrs = true;
+
+ if (is_wrap_attributes_force && input_char !== '/') {
+ var force_first_attr_wrap = false;
+ if (is_wrap_attributes_force_expand_multiline && first_attr) {
+ var is_only_attribute = tail.match(/^\S*(="([^"]|\\")*")?\s*\/?\s*>/) !== null;
+ force_first_attr_wrap = !is_only_attribute;
+ }
+ if (!first_attr || force_first_attr_wrap) {
+ this.print_newline(false, content);
+ this.print_indentation(content);
+ indentAttrs = true;
+ }
}
if (indentAttrs) {
+ has_wrapped_attrs = true;
+
//indent attributes an auto, forced, or forced-align line-wrap
var alignment_size = wrap_attributes_indent_size;
- if (wrap_attributes === 'force-aligned') {
+ if (is_wrap_attributes_force_aligned) {
alignment_size = content.indexOf(' ') + 1;
}
@@ -416,10 +440,12 @@
content.push(indent_character);
}
}
- for (var i = 0; i < content.length; i++) {
- if (content[i] === ' ') {
- first_attr = false;
- break;
+ if (first_attr) {
+ for (var i = 0; i < content.length; i++) {
+ if (content[i] === ' ') {
+ first_attr = false;
+ break;
+ }
}
}
}
@@ -478,7 +504,9 @@
var tag_index;
var tag_offset;
- if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+ if (tag_complete.indexOf('\n') !== -1) { //if there's a line break, thats where the tag name ends
+ tag_index = tag_complete.indexOf('\n');
+ } else if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
tag_index = tag_complete.indexOf(' ');
} else if (tag_complete.charAt(0) === '{') {
tag_index = tag_complete.indexOf('}');
diff --git a/js/test/generated/beautify-html-tests.js b/js/test/generated/beautify-html-tests.js
index f16f0466f..88fd38f3d 100644
--- a/js/test/generated/beautify-html-tests.js
+++ b/js/test/generated/beautify-html-tests.js
@@ -258,87 +258,139 @@ function run_html_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_be
reset_options();
//============================================================
- // Attribute Wrap - (indent_attr = "\n ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force';
+ test_fragment('
This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = "\n ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force';
opts.wrap_line_length = 80;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = "\n ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force';
opts.wrap_attributes_indent_size = 8;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = " ", indent_over80 = "\n")
+ // Attribute Wrap - (indent_attr = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n")
opts.wrap_attributes = 'auto';
opts.wrap_line_length = 80;
opts.wrap_attributes_indent_size = 0;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = " ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'auto';
opts.wrap_line_length = 80;
opts.wrap_attributes_indent_size = 4;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = " ", indent_over80 = " ")
+ // Attribute Wrap - (indent_attr = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = " ")
opts.wrap_attributes = 'auto';
opts.wrap_line_length = 0;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
');
test_fragment('This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('');
- // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force-aligned';
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force-aligned';
opts.wrap_line_length = 80;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
- // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_over80 = "\n ")
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_faligned = " ", indent_attr_first = " ", indent_end = "", indent_end_selfclosing = " ", indent_over80 = "\n ")
opts.wrap_attributes = 'force-aligned';
opts.wrap_attributes_indent_size = 8;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('This is some text
', 'This is some text
');
test_fragment('', '');
test_fragment('', '\n');
test_fragment('', '');
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = "\n ", indent_end = "\n", indent_end_selfclosing = "\n", indent_over80 = "\n ")
+ opts.wrap_attributes = 'force-expand-multiline';
+ opts.wrap_attributes_indent_size = 4;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('', '');
+ test_fragment('', '\n');
+ test_fragment('', '');
+
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = "\n ", indent_end = "\n", indent_end_selfclosing = "\n", indent_over80 = "\n ")
+ opts.wrap_attributes = 'force-expand-multiline';
+ opts.wrap_attributes_indent_size = 4;
+ opts.wrap_line_length = 80;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('', '');
+ test_fragment('', '\n');
+ test_fragment('', '');
+
+ // Attribute Wrap - (indent_attr = "\n ", indent_attr_first = "\n ", indent_end = "\n", indent_end_selfclosing = "\n", indent_over80 = "\n ")
+ opts.wrap_attributes = 'force-expand-multiline';
+ opts.wrap_attributes_indent_size = 8;
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('This is some text
', 'This is some text
');
+ test_fragment('', '');
+ test_fragment('', '\n');
+ test_fragment('', '');
+
reset_options();
//============================================================
diff --git a/test/data/html/tests.js b/test/data/html/tests.js
index 9f390732a..017c44f6a 100644
--- a/test/data/html/tests.js
+++ b/test/data/html/tests.js
@@ -262,6 +262,9 @@ exports.test_data = {
{ name: "wrap_attributes", value: "'force'" }
],
indent_attr: '\\n ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -269,6 +272,9 @@ exports.test_data = {
{ name: "wrap_line_length", value: "80" }
],
indent_attr: '\\n ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -276,6 +282,9 @@ exports.test_data = {
{ name: "wrap_attributes_indent_size", value: "8" }
],
indent_attr: '\\n ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -284,6 +293,9 @@ exports.test_data = {
{ name: "wrap_attributes_indent_size", value: "0" }
],
indent_attr: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n'
}, {
options: [
@@ -292,6 +304,9 @@ exports.test_data = {
{ name: "wrap_attributes_indent_size", value: "4" }
],
indent_attr: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -299,6 +314,9 @@ exports.test_data = {
{ name: "wrap_line_length", value: "0" }
],
indent_attr: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: ' '
}, {
options: [
@@ -306,6 +324,9 @@ exports.test_data = {
],
indent_attr: '\\n ',
indent_attr_faligned: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -314,6 +335,9 @@ exports.test_data = {
],
indent_attr: '\\n ',
indent_attr_faligned: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
}, {
options: [
@@ -322,28 +346,70 @@ exports.test_data = {
],
indent_attr: '\\n ',
indent_attr_faligned: ' ',
+ indent_attr_first: ' ',
+ indent_end: '',
+ indent_end_selfclosing: ' ',
indent_over80: '\\n '
+ }, {
+ options: [
+ { name: "wrap_attributes", value: "'force-expand-multiline'" },
+ { name: "wrap_attributes_indent_size", value: "4" }
+ ],
+ indent_attr: '\\n ',
+ indent_attr_first: '\\n ',
+ indent_end: '\\n',
+ indent_end_selfclosing: '\\n',
+ indent_over80: '\\n '
+ }, {
+ options: [
+ { name: "wrap_attributes", value: "'force-expand-multiline'" },
+ { name: "wrap_attributes_indent_size", value: "4" },
+ { name: "wrap_line_length", value: "80" }
+ ],
+ indent_attr: '\\n ',
+ indent_attr_first: '\\n ',
+ indent_end: '\\n',
+ indent_end_selfclosing: '\\n',
+ indent_over80: '\\n '
+ }, {
+ options: [
+ { name: "wrap_attributes", value: "'force-expand-multiline'" },
+ { name: "wrap_attributes_indent_size", value: "8" }
+ ],
+ indent_attr: '\\n ',
+ indent_attr_first: '\\n ',
+ indent_end: '\\n',
+ indent_end_selfclosing: '\\n',
+ indent_over80: '\\n '
}],
tests: [{
+ fragment: true,
+ input: 'This is some text
',
+ output: 'This is some text
'
+ }, {
+ fragment: true,
+ input: 'This is some text
',
+ output: 'This is some text
'
+ }, {
fragment: true,
input: 'This is some text
',
- output: 'This is some text
'
+ output: 'This is some text
'
}, {
fragment: true,
input: 'This is some text
',
- output: 'This is some text
'
+ output: 'This is some text
'
}, {
fragment: true,
input: '',
- output: ''
+ output: ''
}, {
fragment: true,
input: '',
- output: '\n'
+ output: '\n'
}, {
fragment: true,
input: '',
- output: ''
+ output: ''
}]
}, {
name: "Handlebars Indenting Off",