Support wrapping script and style content

Content inside style and script tags can be wrapped in comment or
cdata to allow the text to contain characters and string that
would otherwise not be allowed in html.

Before this change the html beautifier would not see the wrappers.
This meant that it could incorrectly terminate script or style
tags. It also meant that the wrapper would be passed to child
beautifiers and they would have to handle them.

This change makes the html beautfier recognize the wrappers and
correctly handle formatting of them itself. The child beautifiers
see only the content not the wrapper.

Fixes #1641
This commit is contained in:
Liam Newman 2019-04-16 08:52:06 -07:00
parent d88c6a6462
commit 3296dd0d85
4 changed files with 421 additions and 22 deletions

View File

@ -442,10 +442,12 @@ Beautifier.prototype._handle_text = function(printer, raw_token, last_tag_token)
Beautifier.prototype._print_custom_beatifier_text = function(printer, raw_token, last_tag_token) {
var local = this;
if (raw_token.text !== '') {
printer.print_newline(false);
var text = raw_token.text,
_beautifier,
script_indent_level = 1;
script_indent_level = 1,
pre = '',
post = '';
if (last_tag_token.custom_beautifier_name === 'javascript' && typeof this._js_beautify === 'function') {
_beautifier = this._js_beautify;
} else if (last_tag_token.custom_beautifier_name === 'css' && typeof this._css_beautify === 'function') {
@ -457,7 +459,6 @@ Beautifier.prototype._print_custom_beatifier_text = function(printer, raw_token,
};
}
if (this._options.indent_scripts === "keep") {
script_indent_level = 0;
} else if (this._options.indent_scripts === "separate") {
@ -470,24 +471,67 @@ Beautifier.prototype._print_custom_beatifier_text = function(printer, raw_token,
// we'll be adding one back after the text but before the containing tag.
text = text.replace(/\n[ \t]*$/, '');
if (_beautifier) {
// Handle the case where content is wrapped in a comment or cdata.
if (last_tag_token.custom_beautifier_name !== 'html' &&
text[0] === '<' && text.match(/^(<!--|<!\[CDATA\[)/)) {
var matched = /^(<!--[^\n]*|<!\[CDATA\[)(\n?)([ \t\n]*)([\s\S]*)(-->|]]>)$/.exec(text);
// call the Beautifier if avaliable
var Child_options = function() {
this.eol = '\n';
};
Child_options.prototype = this._options.raw_options;
var child_options = new Child_options();
text = _beautifier(indentation + text, child_options);
} else {
// simply indent the string otherwise
var white = raw_token.whitespace_before;
if (white) {
text = text.replace(new RegExp('\n(' + white + ')?', 'g'), '\n');
// if we start to wrap but don't finish, print raw
if (!matched) {
printer.add_raw_token(raw_token);
return;
}
text = indentation + text.replace(/\n/g, '\n' + indentation);
pre = indentation + matched[1] + '\n';
text = matched[4];
if (matched[5]) {
post = indentation + matched[5];
}
// if there is at least one empty line at the end of this text, strip it
// we'll be adding one back after the text but before the containing tag.
text = text.replace(/\n[ \t]*$/, '');
if (matched[2] || matched[3].indexOf('\n') !== -1) {
// if the first line of the non-comment text has spaces
// use that as the basis for indenting in null case.
matched = matched[3].match(/[ \t]+$/);
if (matched) {
raw_token.whitespace_before = matched[0];
}
}
}
if (text) {
if (_beautifier) {
// call the Beautifier if avaliable
var Child_options = function() {
this.eol = '\n';
};
Child_options.prototype = this._options.raw_options;
var child_options = new Child_options();
text = _beautifier(indentation + text, child_options);
} else {
// simply indent the string otherwise
var white = raw_token.whitespace_before;
if (white) {
text = text.replace(new RegExp('\n(' + white + ')?', 'g'), '\n');
}
text = indentation + text.replace(/\n/g, '\n' + indentation);
}
}
if (pre) {
if (!text) {
text = pre + post;
} else {
text = pre + text + '\n' + post;
}
}
printer.print_newline(false);
if (text) {
raw_token.text = text;
raw_token.whitespace_before = '';

View File

@ -122,7 +122,7 @@ Tokenizer.prototype._get_next_token = function(previous_token, open_token) { //
token = token || this._read_open_handlebars(c, open_token);
token = token || this._read_attribute(c, previous_token, open_token);
token = token || this._read_raw_content(previous_token, open_token);
token = token || this._read_raw_content(c, previous_token, open_token);
token = token || this._read_close(c, open_token);
token = token || this._read_content_word(c);
token = token || this._read_comment(c);
@ -258,19 +258,27 @@ Tokenizer.prototype._is_content_unformatted = function(tag_name) {
// script and style tags should always be read as unformatted content
// finally content_unformatted and unformatted element contents are unformatted
return this._options.void_elements.indexOf(tag_name) === -1 &&
(tag_name === 'script' || tag_name === 'style' ||
this._options.content_unformatted.indexOf(tag_name) !== -1 ||
(this._options.content_unformatted.indexOf(tag_name) !== -1 ||
this._options.unformatted.indexOf(tag_name) !== -1);
};
Tokenizer.prototype._read_raw_content = function(previous_token, open_token) { // jshint unused:false
Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token) { // jshint unused:false
var resulting_string = '';
if (open_token && open_token.text[0] === '{') {
resulting_string = this.__patterns.handlebars_raw_close.read();
} else if (previous_token.type === TOKEN.TAG_CLOSE && (previous_token.opened.text[0] === '<')) {
var tag_name = previous_token.opened.text.substr(1).toLowerCase();
if (this._is_content_unformatted(tag_name)) {
if (tag_name === 'script' || tag_name === 'style') {
// Script and style tags are allowed to have comments wrapping their content
// or just have regular content.
var token = this._read_comment(c);
if (token) {
token.type = TOKEN.TEXT;
return token;
}
resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
} else if (this._is_content_unformatted(tag_name)) {
resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
}
}

View File

@ -412,6 +412,161 @@ function run_html_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_be
'</html>');
//============================================================
// Tests for script and style Commented and cdata wapping (#1641)
reset_options();
set_name('Tests for script and style Commented and cdata wapping (#1641)');
bth(
'<style><!----></style>',
// -- output --
'<style>\n' +
' <!--\n' +
' -->\n' +
'</style>');
bth(
'<style><!--\n' +
'--></style>',
// -- output --
'<style>\n' +
' <!--\n' +
' -->\n' +
'</style>');
bth(
'<style><!-- the rest of this line is ignored\n' +
'\n' +
'\n' +
'\n' +
'--></style>',
// -- output --
'<style>\n' +
' <!-- the rest of this line is ignored\n' +
' -->\n' +
'</style>');
bth(
'<style type="test/null"><!--\n' +
'\n' +
'\t \n' +
'\n' +
'--></style>',
// -- output --
'<style type="test/null">\n' +
' <!--\n' +
' -->\n' +
'</style>');
bth(
'<script><!--\n' +
'console.log("</script>" + "</style>");\n' +
'--></script>',
// -- output --
'<script>\n' +
' <!--\n' +
' console.log("</script>" + "</style>");\n' +
' -->\n' +
'</script>');
// If wrapping is incomplete, print remaining unchanged.
test_fragment(
'<div>\n' +
'<script><!--\n' +
'console.log("</script>" + "</style>");\n' +
' </script>\n' +
'</div>',
// -- output --
'<div>\n' +
' <script><!--\n' +
'console.log("</script>" + "</style>");\n' +
' </script>\n' +
'</div>');
bth(
'<style><!--\n' +
'.selector {\n' +
' font-family: "</script></style>";\n' +
' }\n' +
'--></style>',
// -- output --
'<style>\n' +
' <!--\n' +
' .selector {\n' +
' font-family: "</script></style>";\n' +
' }\n' +
' -->\n' +
'</style>');
bth(
'<script type="test/null">\n' +
' <!--\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
'--></script>',
// -- output --
'<script type="test/null">\n' +
' <!--\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
' -->\n' +
'</script>');
bth(
'<script type="test/null"><!--\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
'--></script>',
// -- output --
'<script type="test/null">\n' +
' <!--\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
' -->\n' +
'</script>');
bth(
'<script><![CDATA[\n' +
'console.log("</script>" + "</style>");\n' +
']]></script>',
// -- output --
'<script>\n' +
' <![CDATA[\n' +
' console.log("</script>" + "</style>");\n' +
' ]]>\n' +
'</script>');
bth(
'<style><![CDATA[\n' +
'.selector {\n' +
' font-family: "</script></style>";\n' +
' }\n' +
']]></style>',
// -- output --
'<style>\n' +
' <![CDATA[\n' +
' .selector {\n' +
' font-family: "</script></style>";\n' +
' }\n' +
' ]]>\n' +
'</style>');
bth(
'<script type="test/null">\n' +
' <![CDATA[\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
']]></script>',
// -- output --
'<script type="test/null">\n' +
' <![CDATA[\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
' ]]>\n' +
'</script>');
bth(
'<script type="test/null"><![CDATA[\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
']]></script>',
// -- output --
'<script type="test/null">\n' +
' <![CDATA[\n' +
' console.log("</script>" + "</style>");\n' +
' console.log("</script>" + "</style>");\n' +
' ]]>\n' +
'</script>');
//============================================================
// Tests for script and style types (issue 453, 821)
reset_options();

View File

@ -214,6 +214,198 @@ exports.test_data = {
input: '<html><head><meta></head><body><div><p>x</p></div></body></html>',
output: '<html>\n<head>\n <meta>\n</head>\n<body>\n <div>\n\n <p>x\n\n </p>\n </div>\n</body>\n</html>'
}]
}, {
name: "Tests for script and style Commented and cdata wapping (#1641)",
description: "Repect comment and cdata wrapping regardless of beautifier",
tests: [{
input: [
'<style><!----></style>'
],
output: [
'<style>',
' <!--',
' -->',
'</style>'
]
}, {
input: [
'<style><!--',
'--></style>'
],
output: [
'<style>',
' <!--',
' -->',
'</style>'
]
}, {
input: [
'<style><!-- the rest of this line is ignored',
'',
'',
'',
'--></style>'
],
output: [
'<style>',
' <!-- the rest of this line is ignored',
' -->',
'</style>'
]
}, {
input: [
'<style type="test/null"><!--',
'',
'\t ',
'',
'--></style>'
],
output: [
'<style type="test/null">',
' <!--',
' -->',
'</style>'
]
}, {
input: [
'<script><!--',
'console.log("</script>" + "</style>");',
'--></script>'
],
output: [
'<script>',
' <!--',
' console.log("</script>" + "</style>");',
' -->',
'</script>'
]
}, {
fragment: true,
comment: 'If wrapping is incomplete, print remaining unchanged.',
input: [
'<div>',
'<script><!--',
'console.log("</script>" + "</style>");',
' </script>',
'</div>'
],
output: [
'<div>',
' <script><!--',
'console.log("</script>" + "</style>");',
' </script>',
'</div>'
]
}, {
input: [
'<style><!--',
'.selector {',
' font-family: "</script></style>";',
' }',
'--></style>'
],
output: [
'<style>',
' <!--',
' .selector {',
' font-family: "</script></style>";',
' }',
' -->',
'</style>'
]
}, {
input: [
'<script type="test/null">',
' <!--',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
'--></script>'
],
output: [
'<script type="test/null">',
' <!--',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
' -->',
'</script>'
]
}, {
input: [
'<script type="test/null"><!--',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
'--></script>'
],
output: [
'<script type="test/null">',
' <!--',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
' -->',
'</script>'
]
}, {
input: [
'<script><![CDATA[',
'console.log("</script>" + "</style>");',
']]></script>'
],
output: [
'<script>',
' <![CDATA[',
' console.log("</script>" + "</style>");',
' ]]>',
'</script>'
]
}, {
input: [
'<style><![CDATA[',
'.selector {',
' font-family: "</script></style>";',
' }',
']]></style>'
],
output: [
'<style>',
' <![CDATA[',
' .selector {',
' font-family: "</script></style>";',
' }',
' ]]>',
'</style>'
]
}, {
input: [
'<script type="test/null">',
' <![CDATA[',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
']]></script>'
],
output: [
'<script type="test/null">',
' <![CDATA[',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
' ]]>',
'</script>'
]
}, {
input: [
'<script type="test/null"><![CDATA[',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
']]></script>'
],
output: [
'<script type="test/null">',
' <![CDATA[',
' console.log("</script>" + "</style>");',
' console.log("</script>" + "</style>");',
' ]]>',
'</script>'
]
}]
}, {
name: "Tests for script and style types (issue 453, 821)",
description: "Only format recognized script types",