html-minifier 1.1.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +43 -30
- package/cli.js +101 -98
- package/dist/htmlminifier.js +32461 -1281
- package/dist/htmlminifier.min.js +14 -2
- package/package.json +13 -7
- package/sample-cli-config-file.conf +21 -14
- package/src/.eslintrc.json +5 -0
- package/src/htmllint.js +139 -142
- package/src/htmlminifier.js +951 -681
- package/src/htmlparser.js +405 -438
- package/src/utils.js +18 -0
package/src/htmlminifier.js
CHANGED
|
@@ -1,838 +1,1108 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var CleanCSS = require('clean-css');
|
|
4
|
+
var HTMLParser = require('./htmlparser').HTMLParser;
|
|
5
|
+
var RelateUrl = require('relateurl');
|
|
6
|
+
var UglifyJS = require('uglify-js');
|
|
7
|
+
var utils = require('./utils');
|
|
8
|
+
|
|
9
|
+
var log;
|
|
10
|
+
if (typeof window !== 'undefined' && typeof console !== 'undefined' && typeof console.log === 'function') {
|
|
11
|
+
log = function(message) {
|
|
12
|
+
// "preserving" `this`
|
|
13
|
+
console.log(message);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
log = function() {};
|
|
18
|
+
}
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
else if (typeof require === 'function') {
|
|
21
|
-
HTMLParser = require('./htmlparser').HTMLParser;
|
|
20
|
+
var trimWhitespace = function(str) {
|
|
21
|
+
if (typeof str !== 'string') {
|
|
22
|
+
return str;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
return str.replace(/^\s+/, '').replace(/\s+$/, '');
|
|
25
|
+
};
|
|
26
|
+
if (String.prototype.trim) {
|
|
27
|
+
trimWhitespace = function(str) {
|
|
25
28
|
if (typeof str !== 'string') {
|
|
26
29
|
return str;
|
|
27
30
|
}
|
|
28
|
-
return str.
|
|
31
|
+
return str.trim();
|
|
29
32
|
};
|
|
30
|
-
|
|
31
|
-
trimWhitespace = function(str) {
|
|
32
|
-
if (typeof str !== 'string') {
|
|
33
|
-
return str;
|
|
34
|
-
}
|
|
35
|
-
return str.trim();
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function collapseWhitespace(str) {
|
|
40
|
-
return str ? str.replace(/[\t\n\r ]+/g, ' ') : str;
|
|
41
|
-
}
|
|
33
|
+
}
|
|
42
34
|
|
|
43
|
-
|
|
35
|
+
function compressWhitespace(spaces) {
|
|
36
|
+
return spaces === '\t' ? spaces : ' ';
|
|
37
|
+
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'q',
|
|
49
|
-
'rt', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong',
|
|
50
|
-
'sub', 'sup', 'svg', 'time', 'tt', 'u', 'var'
|
|
51
|
-
],
|
|
52
|
-
lineBreakBefore = /^[\t ]*[\n\r]+[\t\n\r ]*/,
|
|
53
|
-
lineBreakAfter = /[\t\n\r ]*[\n\r]+[\t ]*$/,
|
|
54
|
-
preserveBefore = lineBreakBefore.test(str) ? '\n' : ' ',
|
|
55
|
-
preserveAfter = lineBreakAfter.test(str) ? '\n' : ' ',
|
|
56
|
-
lineBreakStamp = 'htmlmincollapsedlinebreak';
|
|
57
|
-
|
|
58
|
-
if (prevTag && prevTag !== 'img' && prevTag !== 'input' && (prevTag.substr(0, 1) !== '/'
|
|
59
|
-
|| (prevTag.substr(0, 1) === '/' && (options.collapseInlineTagWhitespace || tags.indexOf(prevTag.substr(1)) === -1)))) {
|
|
60
|
-
str = str.replace(/^\s+/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveBefore : '');
|
|
61
|
-
}
|
|
39
|
+
function collapseWhitespaceAll(str) {
|
|
40
|
+
return str ? str.replace(/[\t\n\r ]+/g, compressWhitespace) : str;
|
|
41
|
+
}
|
|
62
42
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
str = str.replace(/\s+$/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveAfter : '');
|
|
66
|
-
}
|
|
43
|
+
function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
|
|
44
|
+
var lineBreakBefore = '', lineBreakAfter = '';
|
|
67
45
|
|
|
68
|
-
|
|
46
|
+
if (options.preserveLineBreaks) {
|
|
47
|
+
str = str.replace(/^[\t ]*[\n\r][\t\n\r ]*/, function() {
|
|
48
|
+
lineBreakBefore = '\n';
|
|
49
|
+
return '';
|
|
50
|
+
}).replace(/[\t\n\r ]*[\n\r][\t ]*$/, function() {
|
|
51
|
+
lineBreakAfter = '\n';
|
|
52
|
+
return '';
|
|
53
|
+
});
|
|
54
|
+
}
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.replace(lineBreakAfter, lineBreakStamp);
|
|
74
|
-
}
|
|
75
|
-
// strip non space whitespace then compress spaces to one
|
|
76
|
-
return str
|
|
77
|
-
.replace(/[\t\n\r]+/g, ' ').replace(/[ ]+/g, ' ')
|
|
78
|
-
.replace(new RegExp(lineBreakStamp, 'g'), '\n');
|
|
79
|
-
}
|
|
56
|
+
if (trimLeft) {
|
|
57
|
+
str = str.replace(/^\s+/, !lineBreakBefore && options.conservativeCollapse ? compressWhitespace : '');
|
|
58
|
+
}
|
|
80
59
|
|
|
81
|
-
|
|
60
|
+
if (trimRight) {
|
|
61
|
+
str = str.replace(/\s+$/, !lineBreakAfter && options.conservativeCollapse ? compressWhitespace : '');
|
|
82
62
|
}
|
|
83
63
|
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
if (collapseAll) {
|
|
65
|
+
// strip non space whitespace then compress spaces to one
|
|
66
|
+
str = collapseWhitespaceAll(str);
|
|
86
67
|
}
|
|
87
68
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
69
|
+
return lineBreakBefore + str + lineBreakAfter;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var createMapFromString = utils.createMapFromString;
|
|
73
|
+
// non-empty tags that will maintain whitespace around them
|
|
74
|
+
var inlineTags = createMapFromString('a,abbr,acronym,b,bdi,bdo,big,button,cite,code,del,dfn,em,font,i,ins,kbd,mark,math,q,rt,rp,s,samp,small,span,strike,strong,sub,sup,svg,time,tt,u,var');
|
|
75
|
+
// non-empty tags that will maintain whitespace within them
|
|
76
|
+
var inlineTextTags = createMapFromString('a,abbr,acronym,b,big,del,em,font,i,ins,kbd,mark,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var');
|
|
77
|
+
// self-closing tags that will maintain whitespace around them
|
|
78
|
+
var selfClosingInlineTags = createMapFromString('comment,img,input');
|
|
79
|
+
|
|
80
|
+
function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
|
|
81
|
+
var trimLeft = prevTag && !selfClosingInlineTags(prevTag);
|
|
82
|
+
if (trimLeft && !options.collapseInlineTagWhitespace) {
|
|
83
|
+
trimLeft = prevTag.charAt(0) === '/' ? !inlineTags(prevTag.slice(1)) : !inlineTextTags(prevTag);
|
|
84
|
+
}
|
|
85
|
+
var trimRight = nextTag && !selfClosingInlineTags(nextTag);
|
|
86
|
+
if (trimRight && !options.collapseInlineTagWhitespace) {
|
|
87
|
+
trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags(nextTag.slice(1)) : !inlineTags(nextTag);
|
|
88
|
+
}
|
|
89
|
+
return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
|
|
90
|
+
}
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
92
|
+
function isConditionalComment(text) {
|
|
93
|
+
return /^\[if\s[^\]]+\]|\[endif\]$/.test(text);
|
|
94
|
+
}
|
|
100
95
|
|
|
101
|
-
|
|
96
|
+
function isIgnoredComment(text, options) {
|
|
97
|
+
if (/^!/.test(text)) {
|
|
98
|
+
return true;
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
if (options.ignoreCustomComments) {
|
|
102
|
+
for (var i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
|
|
103
|
+
if (options.ignoreCustomComments[i].test(text)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return (/^[^\x20\t\n\f\r"'`=<>]+$/).test(value) &&
|
|
111
|
-
// make sure trailing slash is not interpreted as HTML self-closing tag
|
|
112
|
-
!(isLast && (/\/$/).test(value));
|
|
113
|
-
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
function isEventAttribute(attrName, options) {
|
|
113
|
+
var patterns = options.customEventAttributes;
|
|
114
|
+
if (patterns) {
|
|
115
|
+
for (var i = patterns.length; i--;) {
|
|
116
|
+
if (patterns[i].test(attrName)) {
|
|
118
117
|
return true;
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
120
|
return false;
|
|
122
121
|
}
|
|
122
|
+
else {
|
|
123
|
+
return (/^on[a-z]{3,}$/).test(attrName);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
(tag === 'script' &&
|
|
129
|
-
attrName === 'language' &&
|
|
130
|
-
attrValue === 'javascript') ||
|
|
131
|
-
|
|
132
|
-
(tag === 'form' &&
|
|
133
|
-
attrName === 'method' &&
|
|
134
|
-
attrValue === 'get') ||
|
|
127
|
+
function canRemoveAttributeQuotes(value) {
|
|
128
|
+
// http://mathiasbynens.be/notes/unquoted-attribute-values
|
|
129
|
+
return (/^[^\x20\t\n\f\r"'`=<>]+$/).test(value);
|
|
130
|
+
}
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
function attributesInclude(attributes, attribute) {
|
|
133
|
+
for (var i = attributes.length; i--;) {
|
|
134
|
+
if (attributes[i].name.toLowerCase() === attribute) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
!attributesInclude(attrs, 'src')) ||
|
|
141
|
+
function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
142
|
+
attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
return (
|
|
145
|
+
(tag === 'script' &&
|
|
146
|
+
attrName === 'language' &&
|
|
147
|
+
attrValue === 'javascript') ||
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
);
|
|
152
|
-
}
|
|
149
|
+
(tag === 'form' &&
|
|
150
|
+
attrName === 'method' &&
|
|
151
|
+
attrValue === 'get') ||
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
tag === 'script' &&
|
|
153
|
+
(tag === 'input' &&
|
|
157
154
|
attrName === 'type' &&
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
155
|
+
attrValue === 'text') ||
|
|
156
|
+
|
|
157
|
+
(tag === 'script' &&
|
|
158
|
+
attrName === 'charset' &&
|
|
159
|
+
!attributesInclude(attrs, 'src')) ||
|
|
160
|
+
|
|
161
|
+
(tag === 'a' &&
|
|
162
|
+
attrName === 'name' &&
|
|
163
|
+
attributesInclude(attrs, 'id')) ||
|
|
164
|
+
|
|
165
|
+
(tag === 'area' &&
|
|
166
|
+
attrName === 'shape' &&
|
|
167
|
+
attrValue === 'rect')
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// https://mathiasbynens.be/demo/javascript-mime-type
|
|
172
|
+
// https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
|
|
173
|
+
var executableScriptsMimetypes = utils.createMap([
|
|
174
|
+
'text/javascript',
|
|
175
|
+
'text/ecmascript',
|
|
176
|
+
'text/jscript',
|
|
177
|
+
'application/javascript',
|
|
178
|
+
'application/x-javascript',
|
|
179
|
+
'application/ecmascript'
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
function isScriptTypeAttribute(tag, attrName, attrValue) {
|
|
183
|
+
return (
|
|
184
|
+
tag === 'script' &&
|
|
185
|
+
attrName === 'type' &&
|
|
186
|
+
executableScriptsMimetypes(trimWhitespace(attrValue.toLowerCase()))
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isExecutableScript(tag, attrs) {
|
|
191
|
+
if (tag !== 'script') {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
195
|
+
var attrName = attrs[i].name.toLowerCase();
|
|
196
|
+
if (attrName === 'type') {
|
|
197
|
+
var attrValue = trimWhitespace(attrs[i].value.split(/;/, 2)[0]).toLowerCase();
|
|
198
|
+
return attrValue === '' || executableScriptsMimetypes(attrValue);
|
|
183
199
|
}
|
|
184
|
-
return true;
|
|
185
200
|
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
186
203
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
attrName === 'type' &&
|
|
191
|
-
trimWhitespace(attrValue.toLowerCase()) === 'text/css'
|
|
192
|
-
);
|
|
204
|
+
function isStyleSheet(tag, attrs) {
|
|
205
|
+
if (tag !== 'style') {
|
|
206
|
+
return false;
|
|
193
207
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
function isBooleanAttribute(attrName, attrValue) {
|
|
200
|
-
var isSimpleBoolean = (/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/i).test(attrName);
|
|
201
|
-
if (isSimpleBoolean) {
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
var attrValueEnumeration = enumeratedAttributeValues[attrName.toLowerCase()];
|
|
206
|
-
if (!attrValueEnumeration) {
|
|
207
|
-
return false;
|
|
208
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
209
|
+
var attrName = attrs[i].name.toLowerCase();
|
|
210
|
+
if (attrName === 'type') {
|
|
211
|
+
var attrValue = trimWhitespace(attrs[i].value).toLowerCase();
|
|
212
|
+
return attrValue === '' || attrValue === 'text/css';
|
|
208
213
|
}
|
|
209
|
-
|
|
210
|
-
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isStyleLinkTypeAttribute(tag, attrName, attrValue) {
|
|
219
|
+
return (
|
|
220
|
+
(tag === 'style' || tag === 'link') &&
|
|
221
|
+
attrName === 'type' &&
|
|
222
|
+
trimWhitespace(attrValue.toLowerCase()) === 'text/css'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
var isSimpleBoolean = createMapFromString('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible');
|
|
227
|
+
var isBooleanValue = createMapFromString('true,false');
|
|
228
|
+
|
|
229
|
+
function isBooleanAttribute(attrName, attrValue) {
|
|
230
|
+
return isSimpleBoolean(attrName) || attrName === 'draggable' && !isBooleanValue(attrValue);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isUriTypeAttribute(attrName, tag) {
|
|
234
|
+
return (
|
|
235
|
+
((/^(?:a|area|link|base)$/).test(tag) && attrName === 'href') ||
|
|
236
|
+
(tag === 'img' && (/^(?:src|longdesc|usemap)$/).test(attrName)) ||
|
|
237
|
+
(tag === 'object' && (/^(?:classid|codebase|data|usemap)$/).test(attrName)) ||
|
|
238
|
+
(tag === 'q' && attrName === 'cite') ||
|
|
239
|
+
(tag === 'blockquote' && attrName === 'cite') ||
|
|
240
|
+
((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
|
|
241
|
+
(tag === 'form' && attrName === 'action') ||
|
|
242
|
+
(tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
|
|
243
|
+
(tag === 'head' && attrName === 'profile') ||
|
|
244
|
+
(tag === 'script' && (attrName === 'src' || attrName === 'for'))
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isNumberTypeAttribute(attrName, tag) {
|
|
249
|
+
return (
|
|
250
|
+
((/^(?:a|area|object|button)$/).test(tag) && attrName === 'tabindex') ||
|
|
251
|
+
(tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
|
|
252
|
+
(tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
|
|
253
|
+
(tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
|
|
254
|
+
(tag === 'colgroup' && attrName === 'span') ||
|
|
255
|
+
(tag === 'col' && attrName === 'span') ||
|
|
256
|
+
((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isCanonicalURL(tag, attrs) {
|
|
261
|
+
if (tag !== 'link') {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
265
|
+
if (attrs[i].name === 'rel' && attrs[i].value === 'canonical') {
|
|
266
|
+
return true;
|
|
211
267
|
}
|
|
212
268
|
}
|
|
269
|
+
}
|
|
213
270
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
((/^(?:a|area|link|base)$/).test(tag) && attrName === 'href') ||
|
|
217
|
-
(tag === 'img' && (/^(?:src|longdesc|usemap)$/).test(attrName)) ||
|
|
218
|
-
(tag === 'object' && (/^(?:classid|codebase|data|usemap)$/).test(attrName)) ||
|
|
219
|
-
(tag === 'q' && attrName === 'cite') ||
|
|
220
|
-
(tag === 'blockquote' && attrName === 'cite') ||
|
|
221
|
-
((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
|
|
222
|
-
(tag === 'form' && attrName === 'action') ||
|
|
223
|
-
(tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
|
|
224
|
-
(tag === 'head' && attrName === 'profile') ||
|
|
225
|
-
(tag === 'script' && (attrName === 'src' || attrName === 'for'))
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function isNumberTypeAttribute(attrName, tag) {
|
|
230
|
-
return (
|
|
231
|
-
((/^(?:a|area|object|button)$/).test(tag) && attrName === 'tabindex') ||
|
|
232
|
-
(tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
|
|
233
|
-
(tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
|
|
234
|
-
(tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
|
|
235
|
-
(tag === 'colgroup' && attrName === 'span') ||
|
|
236
|
-
(tag === 'col' && attrName === 'span') ||
|
|
237
|
-
((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
|
|
238
|
-
);
|
|
239
|
-
}
|
|
271
|
+
var fnPrefix = '!function(){';
|
|
272
|
+
var fnSuffix = '}();';
|
|
240
273
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return minified.slice(12, minified.length - 4).replace(/"/g, '"');
|
|
248
|
-
}
|
|
249
|
-
return attrValue;
|
|
250
|
-
}
|
|
251
|
-
else if (attrName === 'class') {
|
|
252
|
-
return collapseWhitespace(trimWhitespace(attrValue));
|
|
253
|
-
}
|
|
254
|
-
else if (isUriTypeAttribute(attrName, tag)) {
|
|
255
|
-
attrValue = trimWhitespace(attrValue);
|
|
256
|
-
if (options.minifyURLs) {
|
|
257
|
-
return minifyURLs(attrValue, options.minifyURLs);
|
|
258
|
-
}
|
|
259
|
-
return attrValue;
|
|
260
|
-
}
|
|
261
|
-
else if (isNumberTypeAttribute(attrName, tag)) {
|
|
262
|
-
return trimWhitespace(attrValue);
|
|
263
|
-
}
|
|
264
|
-
else if (attrName === 'style') {
|
|
265
|
-
attrValue = trimWhitespace(attrValue);
|
|
266
|
-
if (attrValue) {
|
|
267
|
-
attrValue = attrValue.replace(/\s*;\s*$/, '');
|
|
268
|
-
}
|
|
269
|
-
if (options.minifyCSS) {
|
|
270
|
-
return minifyCSS(attrValue, options.minifyCSS, true);
|
|
271
|
-
}
|
|
272
|
-
return attrValue;
|
|
273
|
-
}
|
|
274
|
-
else if (isMetaViewport(tag, attrs) && attrName === 'content') {
|
|
275
|
-
attrValue = attrValue.replace(/1\.0/g, '1').replace(/\s+/g, '');
|
|
276
|
-
}
|
|
277
|
-
else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
|
|
278
|
-
attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
|
|
274
|
+
function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
|
|
275
|
+
if (attrValue && isEventAttribute(attrName, options)) {
|
|
276
|
+
attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '').replace(/\s*;$/, '');
|
|
277
|
+
if (options.minifyJS) {
|
|
278
|
+
var minified = minifyJS(fnPrefix + attrValue + fnSuffix, options.minifyJS);
|
|
279
|
+
return minified.slice(fnPrefix.length, -fnSuffix.length);
|
|
279
280
|
}
|
|
280
281
|
return attrValue;
|
|
281
282
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
283
|
+
else if (attrName === 'class') {
|
|
284
|
+
return collapseWhitespaceAll(trimWhitespace(attrValue));
|
|
285
|
+
}
|
|
286
|
+
else if (isUriTypeAttribute(attrName, tag)) {
|
|
287
|
+
attrValue = trimWhitespace(attrValue);
|
|
288
|
+
if (options.minifyURLs && !isCanonicalURL(tag, attrs)) {
|
|
289
|
+
return minifyURLs(attrValue, options.minifyURLs);
|
|
291
290
|
}
|
|
291
|
+
return attrValue;
|
|
292
292
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// See https://github.com/jakubpawlowicz/clean-css/issues/418
|
|
296
|
-
function wrapCSS(text) {
|
|
297
|
-
return '*{' + text + '}';
|
|
293
|
+
else if (isNumberTypeAttribute(attrName, tag)) {
|
|
294
|
+
return trimWhitespace(attrValue);
|
|
298
295
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return matches[1];
|
|
296
|
+
else if (attrName === 'style') {
|
|
297
|
+
attrValue = trimWhitespace(attrValue);
|
|
298
|
+
if (attrValue) {
|
|
299
|
+
attrValue = attrValue.replace(/\s*;\s*$/, '');
|
|
304
300
|
}
|
|
305
|
-
|
|
306
|
-
return
|
|
301
|
+
if (options.minifyCSS) {
|
|
302
|
+
return minifyCSS(attrValue, options.minifyCSS, true);
|
|
307
303
|
}
|
|
304
|
+
return attrValue;
|
|
308
305
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
.
|
|
313
|
-
.
|
|
306
|
+
else if (isMetaViewport(tag, attrs) && attrName === 'content') {
|
|
307
|
+
attrValue = attrValue.replace(/\s+/g, '').replace(/[0-9]+\.[0-9]+/g, function(numString) {
|
|
308
|
+
// "0.90000" -> "0.9"
|
|
309
|
+
// "1.0" -> "1"
|
|
310
|
+
// "1.0001" -> "1.0001" (unchanged)
|
|
311
|
+
return (+numString).toString();
|
|
312
|
+
});
|
|
314
313
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// "/* ]]> */" or "// ]]>"
|
|
321
|
-
.replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/, '');
|
|
314
|
+
else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
|
|
315
|
+
attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
|
|
316
|
+
}
|
|
317
|
+
else if (tag === 'script' && attrName === 'type') {
|
|
318
|
+
attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
|
|
322
319
|
}
|
|
320
|
+
return attrValue;
|
|
321
|
+
}
|
|
323
322
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
323
|
+
function isMetaViewport(tag, attrs) {
|
|
324
|
+
if (tag !== 'meta') {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
328
|
+
if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
|
|
329
|
+
return true;
|
|
330
330
|
}
|
|
331
|
-
return text;
|
|
332
331
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Wrap CSS declarations for CleanCSS > 3.x
|
|
335
|
+
// See https://github.com/jakubpawlowicz/clean-css/issues/418
|
|
336
|
+
function wrapCSS(text) {
|
|
337
|
+
return '*{' + text + '}';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function unwrapCSS(text) {
|
|
341
|
+
var matches = text.match(/^\*\{([\s\S]*)\}$/m);
|
|
342
|
+
if (matches && matches[1]) {
|
|
343
|
+
return matches[1];
|
|
345
344
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return (/^(?:html|t?body|t?head|tfoot|tr|td|th|dt|dd|option|colgroup|source|track)$/).test(tag);
|
|
345
|
+
else {
|
|
346
|
+
return text;
|
|
349
347
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function cleanConditionalComment(comment, options) {
|
|
351
|
+
return options.processConditionalComments ? comment.replace(/^(\[if\s[^\]]+\]>)([\s\S]*?)(<!\[endif\])$/, function(match, prefix, text, suffix) {
|
|
352
|
+
return prefix + minify(text, options, true) + suffix;
|
|
353
|
+
}) : comment;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function processScript(text, options, currentAttrs) {
|
|
357
|
+
for (var i = 0, len = currentAttrs.length; i < len; i++) {
|
|
358
|
+
if (currentAttrs[i].name.toLowerCase() === 'type' &&
|
|
359
|
+
options.processScripts.indexOf(currentAttrs[i].value) > -1) {
|
|
360
|
+
return minify(text, options);
|
|
361
361
|
}
|
|
362
|
-
return false;
|
|
363
362
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
363
|
+
return text;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
|
|
367
|
+
// with the following deviations:
|
|
368
|
+
// - retain <body> if followed by <noscript>
|
|
369
|
+
// - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow http://www.w3.org/TR/html5/syntax.html#optional-tags
|
|
370
|
+
// - retain all tags which are adjacent to non-standard HTML tags
|
|
371
|
+
var optionalStartTags = createMapFromString('html,head,body,colgroup,tbody');
|
|
372
|
+
var optionalEndTags = createMapFromString('html,head,body,li,dt,dd,p,rb,rt,rtc,rp,optgroup,option,colgroup,caption,thead,tbody,tfoot,tr,td,th');
|
|
373
|
+
var headerTags = createMapFromString('meta,link,script,style,template,noscript');
|
|
374
|
+
var descriptionTags = createMapFromString('dt,dd');
|
|
375
|
+
var pBlockTags = createMapFromString('address,article,aside,blockquote,details,div,dl,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,menu,nav,ol,p,pre,section,table,ul');
|
|
376
|
+
var pInlineTags = createMapFromString('a,audio,del,ins,map,noscript,video');
|
|
377
|
+
var rubyTags = createMapFromString('rb,rt,rtc,rp');
|
|
378
|
+
var rtcTag = createMapFromString('rb,rtc,rp');
|
|
379
|
+
var optionTag = createMapFromString('option,optgroup');
|
|
380
|
+
var tableContentTags = createMapFromString('tbody,tfoot');
|
|
381
|
+
var tableSectionTags = createMapFromString('thead,tbody,tfoot');
|
|
382
|
+
var cellTags = createMapFromString('td,th');
|
|
383
|
+
var topLevelTags = createMapFromString('html,head,body');
|
|
384
|
+
var compactTags = createMapFromString('html,body');
|
|
385
|
+
var looseTags = createMapFromString('head,colgroup,caption');
|
|
386
|
+
var trailingTags = createMapFromString('dt,thead');
|
|
387
|
+
var htmlTags = createMapFromString('a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,element,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,isindex,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,multicol,nav,nobr,noembed,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp');
|
|
388
|
+
|
|
389
|
+
function canRemoveParentTag(optionalStartTag, tag) {
|
|
390
|
+
switch (optionalStartTag) {
|
|
391
|
+
case 'html':
|
|
392
|
+
case 'head':
|
|
393
|
+
return true;
|
|
394
|
+
case 'body':
|
|
395
|
+
return !headerTags(tag);
|
|
396
|
+
case 'colgroup':
|
|
397
|
+
return tag === 'col';
|
|
398
|
+
case 'tbody':
|
|
399
|
+
return tag === 'tr';
|
|
379
400
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function isStartTagMandatory(optionalEndTag, tag) {
|
|
405
|
+
switch (tag) {
|
|
406
|
+
case 'colgroup':
|
|
407
|
+
return optionalEndTag === 'colgroup';
|
|
408
|
+
case 'tbody':
|
|
409
|
+
return tableSectionTags(optionalEndTag);
|
|
383
410
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function canRemovePrecedingTag(optionalEndTag, tag) {
|
|
415
|
+
switch (optionalEndTag) {
|
|
416
|
+
case 'html':
|
|
417
|
+
case 'head':
|
|
418
|
+
case 'body':
|
|
419
|
+
case 'colgroup':
|
|
420
|
+
case 'caption':
|
|
421
|
+
return true;
|
|
422
|
+
case 'li':
|
|
423
|
+
case 'optgroup':
|
|
424
|
+
case 'tr':
|
|
425
|
+
return tag === optionalEndTag;
|
|
426
|
+
case 'dt':
|
|
427
|
+
case 'dd':
|
|
428
|
+
return descriptionTags(tag);
|
|
429
|
+
case 'p':
|
|
430
|
+
return pBlockTags(tag);
|
|
431
|
+
case 'rb':
|
|
432
|
+
case 'rt':
|
|
433
|
+
case 'rp':
|
|
434
|
+
return rubyTags(tag);
|
|
435
|
+
case 'rtc':
|
|
436
|
+
return rtcTag(tag);
|
|
437
|
+
case 'option':
|
|
438
|
+
return optionTag(tag);
|
|
439
|
+
case 'thead':
|
|
440
|
+
case 'tbody':
|
|
441
|
+
return tableContentTags(tag);
|
|
442
|
+
case 'tfoot':
|
|
443
|
+
return tag === 'tbody';
|
|
444
|
+
case 'td':
|
|
445
|
+
case 'th':
|
|
446
|
+
return cellTags(tag);
|
|
387
447
|
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
388
450
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
attrValue = options.preventAttributesEscaping ? attr.value : attr.escaped,
|
|
393
|
-
attrQuote = options.preventAttributesEscaping ? attr.quote : (options.quoteCharacter === '\'' ? '\'' : '"'),
|
|
394
|
-
attrFragment,
|
|
395
|
-
emittedAttrValue,
|
|
396
|
-
isTerminalOfUnarySlash = unarySlash && index === attrs.length - 1;
|
|
397
|
-
|
|
398
|
-
if ((options.removeRedundantAttributes &&
|
|
399
|
-
isAttributeRedundant(tag, attrName, attrValue, attrs))
|
|
400
|
-
||
|
|
401
|
-
(options.removeScriptTypeAttributes &&
|
|
402
|
-
isScriptTypeAttribute(tag, attrName, attrValue))
|
|
403
|
-
||
|
|
404
|
-
(options.removeStyleLinkTypeAttributes &&
|
|
405
|
-
isStyleLinkTypeAttribute(tag, attrName, attrValue))) {
|
|
406
|
-
return '';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
|
|
410
|
-
|
|
411
|
-
if (attrValue !== undefined && !options.removeAttributeQuotes ||
|
|
412
|
-
!canRemoveAttributeQuotes(attrValue, isLast) || isTerminalOfUnarySlash) {
|
|
413
|
-
emittedAttrValue = attrQuote + attrValue + attrQuote;
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
emittedAttrValue = attrValue;
|
|
417
|
-
}
|
|
451
|
+
var reEmptyAttribute = new RegExp(
|
|
452
|
+
'^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
|
|
453
|
+
'?:down|up|over|move|out)|key(?:press|down|up)))$');
|
|
418
454
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
455
|
+
function canDeleteEmptyAttribute(tag, attrName, attrValue) {
|
|
456
|
+
var isValueEmpty = !attrValue || (/^\s*$/).test(attrValue);
|
|
457
|
+
if (isValueEmpty) {
|
|
458
|
+
return (
|
|
459
|
+
(tag === 'input' && attrName === 'value') ||
|
|
460
|
+
reEmptyAttribute.test(attrName));
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
423
464
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
else {
|
|
429
|
-
attrFragment = attrName + attr.customAssign + emittedAttrValue;
|
|
465
|
+
function hasAttrName(name, attrs) {
|
|
466
|
+
for (var i = attrs.length - 1; i >= 0; i--) {
|
|
467
|
+
if (attrs[i].name === name) {
|
|
468
|
+
return true;
|
|
430
469
|
}
|
|
431
|
-
|
|
432
|
-
return (' ' + attr.customOpen + attrFragment + attr.customClose);
|
|
433
470
|
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
434
473
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
474
|
+
function canRemoveElement(tag, attrs) {
|
|
475
|
+
switch (tag) {
|
|
476
|
+
case 'textarea':
|
|
477
|
+
return false;
|
|
478
|
+
case 'audio':
|
|
479
|
+
case 'script':
|
|
480
|
+
case 'video':
|
|
481
|
+
if (hasAttrName('src', attrs)) {
|
|
482
|
+
return false;
|
|
444
483
|
}
|
|
445
|
-
|
|
484
|
+
break;
|
|
485
|
+
case 'iframe':
|
|
486
|
+
if (hasAttrName('src', attrs) || hasAttrName('srcdoc', attrs)) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
case 'object':
|
|
491
|
+
if (hasAttrName('data', attrs)) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
break;
|
|
495
|
+
case 'applet':
|
|
496
|
+
if (hasAttrName('code', attrs)) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function canCollapseWhitespace(tag) {
|
|
505
|
+
return !(/^(?:script|style|pre|textarea)$/.test(tag));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function canTrimWhitespace(tag) {
|
|
509
|
+
return !(/^(?:pre|textarea)$/.test(tag));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function normalizeAttribute(attr, attrs, tag, hasUnarySlash, index, options, isLast) {
|
|
513
|
+
|
|
514
|
+
var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
|
|
515
|
+
attrValue = attr.value,
|
|
516
|
+
attrQuote = attr.quote,
|
|
517
|
+
attrFragment,
|
|
518
|
+
emittedAttrValue;
|
|
519
|
+
|
|
520
|
+
if ((options.removeRedundantAttributes &&
|
|
521
|
+
isAttributeRedundant(tag, attrName, attrValue, attrs))
|
|
522
|
+
||
|
|
523
|
+
(options.removeScriptTypeAttributes &&
|
|
524
|
+
isScriptTypeAttribute(tag, attrName, attrValue))
|
|
525
|
+
||
|
|
526
|
+
(options.removeStyleLinkTypeAttributes &&
|
|
527
|
+
isStyleLinkTypeAttribute(tag, attrName, attrValue))) {
|
|
528
|
+
return '';
|
|
446
529
|
}
|
|
447
530
|
|
|
448
|
-
|
|
449
|
-
if (typeof options !== 'object') {
|
|
450
|
-
options = { };
|
|
451
|
-
}
|
|
531
|
+
attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
|
|
452
532
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
533
|
+
if (options.removeEmptyAttributes &&
|
|
534
|
+
canDeleteEmptyAttribute(tag, attrName, attrValue)) {
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
456
537
|
|
|
457
|
-
|
|
458
|
-
|
|
538
|
+
if (attrValue !== undefined && !options.removeAttributeQuotes ||
|
|
539
|
+
!canRemoveAttributeQuotes(attrValue)) {
|
|
540
|
+
if (!options.preventAttributesEscaping) {
|
|
541
|
+
if (options.quoteCharacter !== undefined) {
|
|
542
|
+
attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
|
|
459
543
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
544
|
+
else {
|
|
545
|
+
var apos = (attrValue.match(/'/g) || []).length;
|
|
546
|
+
var quot = (attrValue.match(/"/g) || []).length;
|
|
547
|
+
attrQuote = apos < quot ? '\'' : '"';
|
|
464
548
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return __RelateUrl.relate(text, options);
|
|
549
|
+
if (attrQuote === '"') {
|
|
550
|
+
attrValue = attrValue.replace(/"/g, '"');
|
|
468
551
|
}
|
|
469
552
|
else {
|
|
470
|
-
|
|
553
|
+
attrValue = attrValue.replace(/'/g, ''');
|
|
471
554
|
}
|
|
472
555
|
}
|
|
473
|
-
|
|
474
|
-
|
|
556
|
+
emittedAttrValue = attrQuote + attrValue + attrQuote;
|
|
557
|
+
if (!isLast && !options.removeTagWhitespace) {
|
|
558
|
+
emittedAttrValue += ' ';
|
|
475
559
|
}
|
|
476
|
-
|
|
560
|
+
}
|
|
561
|
+
// make sure trailing slash is not interpreted as HTML self-closing tag
|
|
562
|
+
else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) {
|
|
563
|
+
emittedAttrValue = attrValue;
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
emittedAttrValue = attrValue + ' ';
|
|
477
567
|
}
|
|
478
568
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
569
|
+
if (attrValue === undefined || (options.collapseBooleanAttributes &&
|
|
570
|
+
isBooleanAttribute(attrName.toLowerCase(), attrValue.toLowerCase()))) {
|
|
571
|
+
attrFragment = attrName;
|
|
572
|
+
if (!isLast) {
|
|
573
|
+
attrFragment += ' ';
|
|
482
574
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
// try to get global reference first
|
|
490
|
-
var __UglifyJS = global.UglifyJS;
|
|
491
|
-
|
|
492
|
-
if (typeof __UglifyJS === 'undefined' && typeof require === 'function') {
|
|
493
|
-
__UglifyJS = require('uglify-js');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// noop
|
|
497
|
-
if (!__UglifyJS) {
|
|
498
|
-
return text;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (__UglifyJS.minify) {
|
|
502
|
-
return __UglifyJS.minify(text, options).code;
|
|
503
|
-
}
|
|
504
|
-
else if (__UglifyJS.parse) {
|
|
505
|
-
|
|
506
|
-
var ast = __UglifyJS.parse(text);
|
|
507
|
-
ast.figure_out_scope();
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
attrFragment = attrName + attr.customAssign + emittedAttrValue;
|
|
578
|
+
}
|
|
508
579
|
|
|
509
|
-
|
|
510
|
-
|
|
580
|
+
return attr.customOpen + attrFragment + attr.customClose;
|
|
581
|
+
}
|
|
511
582
|
|
|
512
|
-
|
|
513
|
-
|
|
583
|
+
function processOptions(options) {
|
|
584
|
+
if (!('includeAutoGeneratedTags' in options)) {
|
|
585
|
+
options.includeAutoGeneratedTags = true;
|
|
586
|
+
}
|
|
514
587
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
588
|
+
var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];
|
|
589
|
+
for (var i = 0, len = defaultTesters.length; i < len; i++) {
|
|
590
|
+
if (!options[defaultTesters[i]]) {
|
|
591
|
+
options[defaultTesters[i]] = function() {
|
|
592
|
+
return false;
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|
|
518
596
|
|
|
519
|
-
|
|
520
|
-
|
|
597
|
+
if (options.minifyURLs && typeof options.minifyURLs !== 'object') {
|
|
598
|
+
options.minifyURLs = { };
|
|
599
|
+
}
|
|
521
600
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return text;
|
|
526
|
-
}
|
|
601
|
+
if (options.minifyJS) {
|
|
602
|
+
if (typeof options.minifyJS !== 'object') {
|
|
603
|
+
options.minifyJS = { };
|
|
527
604
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
return text;
|
|
605
|
+
options.minifyJS.fromString = true;
|
|
606
|
+
(options.minifyJS.output || (options.minifyJS.output = { })).inline_script = true;
|
|
532
607
|
}
|
|
533
608
|
|
|
534
|
-
|
|
535
|
-
if (typeof options !== 'object') {
|
|
536
|
-
options = { };
|
|
609
|
+
if (options.minifyCSS) {
|
|
610
|
+
if (typeof options.minifyCSS !== 'object') {
|
|
611
|
+
options.minifyCSS = { };
|
|
537
612
|
}
|
|
538
|
-
if (typeof options.advanced === 'undefined') {
|
|
539
|
-
options.advanced = false;
|
|
613
|
+
if (typeof options.minifyCSS.advanced === 'undefined') {
|
|
614
|
+
options.minifyCSS.advanced = false;
|
|
540
615
|
}
|
|
541
|
-
|
|
542
|
-
|
|
616
|
+
}
|
|
617
|
+
}
|
|
543
618
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
619
|
+
function minifyURLs(text, options) {
|
|
620
|
+
try {
|
|
621
|
+
return RelateUrl.relate(text, options);
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
log(err);
|
|
625
|
+
return text;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function minifyJS(text, options) {
|
|
630
|
+
var start = text.match(/^\s*<!--.*/);
|
|
631
|
+
var code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
|
|
632
|
+
try {
|
|
633
|
+
return UglifyJS.minify(code, options).code;
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
log(err);
|
|
637
|
+
return text;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function minifyCSS(text, options, inline) {
|
|
642
|
+
var start = text.match(/^\s*<!--/);
|
|
643
|
+
var style = start ? text.slice(start[0].length).replace(/-->\s*$/, '') : text;
|
|
644
|
+
try {
|
|
645
|
+
var cleanCSS = new CleanCSS(options);
|
|
646
|
+
if (inline) {
|
|
647
|
+
return unwrapCSS(cleanCSS.minify(wrapCSS(style)).styles);
|
|
557
648
|
}
|
|
558
|
-
|
|
559
|
-
|
|
649
|
+
else {
|
|
650
|
+
return cleanCSS.minify(style).styles;
|
|
560
651
|
}
|
|
652
|
+
}
|
|
653
|
+
catch (err) {
|
|
654
|
+
log(err);
|
|
561
655
|
return text;
|
|
562
656
|
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function uniqueId(value) {
|
|
660
|
+
var id;
|
|
661
|
+
do {
|
|
662
|
+
id = Math.random().toString(36).slice(2);
|
|
663
|
+
} while (~value.indexOf(id));
|
|
664
|
+
return id;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function minify(value, options, partialMarkup) {
|
|
668
|
+
options = options || {};
|
|
669
|
+
var optionsStack = [];
|
|
670
|
+
processOptions(options);
|
|
671
|
+
value = options.collapseWhitespace ? trimWhitespace(value) : value;
|
|
672
|
+
|
|
673
|
+
var buffer = [ ],
|
|
674
|
+
charsPrevTag,
|
|
675
|
+
currentChars = '',
|
|
676
|
+
hasChars,
|
|
677
|
+
currentTag = '',
|
|
678
|
+
currentAttrs = [],
|
|
679
|
+
stackNoTrimWhitespace = [],
|
|
680
|
+
stackNoCollapseWhitespace = [],
|
|
681
|
+
optionalStartTag = '',
|
|
682
|
+
optionalEndTag = '',
|
|
683
|
+
lint = options.lint,
|
|
684
|
+
t = Date.now(),
|
|
685
|
+
ignoredMarkupChunks = [ ],
|
|
686
|
+
ignoredCustomMarkupChunks = [ ],
|
|
687
|
+
uidIgnore,
|
|
688
|
+
uidAttr;
|
|
689
|
+
|
|
690
|
+
// temporarily replace ignored chunks with comments,
|
|
691
|
+
// so that we don't have to worry what's there.
|
|
692
|
+
// for all we care there might be
|
|
693
|
+
// completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
|
|
694
|
+
value = value.replace(/<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g, function(match, group1) {
|
|
695
|
+
if (!uidIgnore) {
|
|
696
|
+
uidIgnore = uniqueId(value);
|
|
697
|
+
}
|
|
698
|
+
var token = '<!--!' + uidIgnore + ignoredMarkupChunks.length + '-->';
|
|
699
|
+
ignoredMarkupChunks.push(group1);
|
|
700
|
+
return token;
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
var customFragments = (options.ignoreCustomFragments || [
|
|
704
|
+
/<%[\s\S]*?%>/,
|
|
705
|
+
/<\?[\s\S]*?\?>/
|
|
706
|
+
]).map(function(re) {
|
|
707
|
+
return re.source;
|
|
708
|
+
});
|
|
709
|
+
if (customFragments.length) {
|
|
710
|
+
var reCustomIgnore = new RegExp('\\s*(?:' + customFragments.join('|') + ')+\\s*', 'g');
|
|
711
|
+
// temporarily replace custom ignored fragments with unique attributes
|
|
712
|
+
value = value.replace(reCustomIgnore, function(match) {
|
|
713
|
+
if (!uidAttr) {
|
|
714
|
+
uidAttr = uniqueId(value);
|
|
715
|
+
}
|
|
716
|
+
var token = uidAttr + ignoredCustomMarkupChunks.length;
|
|
717
|
+
ignoredCustomMarkupChunks.push(match);
|
|
718
|
+
return '\t' + token + '\t';
|
|
719
|
+
});
|
|
720
|
+
}
|
|
563
721
|
|
|
564
|
-
function
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
var optionsStack = [];
|
|
568
|
-
|
|
569
|
-
value = trimWhitespace(value);
|
|
570
|
-
setDefaultTesters(options);
|
|
571
|
-
|
|
572
|
-
var results = [ ],
|
|
573
|
-
buffer = [ ],
|
|
574
|
-
currentChars = '',
|
|
575
|
-
currentTag = '',
|
|
576
|
-
currentAttrs = [],
|
|
577
|
-
stackNoTrimWhitespace = [],
|
|
578
|
-
stackNoCollapseWhitespace = [],
|
|
579
|
-
lint = options.lint,
|
|
580
|
-
t = new Date(),
|
|
581
|
-
ignoredMarkupChunks = [ ],
|
|
582
|
-
ignoredCustomMarkupChunks = [ ],
|
|
583
|
-
reIgnore = /<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g,
|
|
584
|
-
uidAttr = ' htmlmin' + (Math.random() + '').slice(2) + ' ',
|
|
585
|
-
reCustomIgnore,
|
|
586
|
-
customFragments;
|
|
587
|
-
|
|
588
|
-
if (options.ignoreCustomFragments) {
|
|
722
|
+
function _canCollapseWhitespace(tag, attrs) {
|
|
723
|
+
return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
|
|
724
|
+
}
|
|
589
725
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
reCustomIgnore = new RegExp('(?:' + customFragments.join('|') + ')', 'g');
|
|
726
|
+
function _canTrimWhitespace(tag, attrs) {
|
|
727
|
+
return canTrimWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
|
|
728
|
+
}
|
|
594
729
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
730
|
+
function removeStartTag() {
|
|
731
|
+
var index = buffer.length - 1;
|
|
732
|
+
while (index > 0 && !/^<[^/!]/.test(buffer[index])) {
|
|
733
|
+
index--;
|
|
600
734
|
}
|
|
735
|
+
buffer.length = Math.max(0, index);
|
|
736
|
+
}
|
|
601
737
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
value = value.replace(reIgnore, function(match, group1) {
|
|
607
|
-
ignoredMarkupChunks.push(group1);
|
|
608
|
-
return '<!-- htmlmin:temp -->';
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
function _canCollapseWhitespace(tag, attrs) {
|
|
612
|
-
return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
|
|
738
|
+
function removeEndTag() {
|
|
739
|
+
var index = buffer.length - 1;
|
|
740
|
+
while (index > 0 && !/^<\//.test(buffer[index])) {
|
|
741
|
+
index--;
|
|
613
742
|
}
|
|
743
|
+
buffer.length = Math.max(0, index);
|
|
744
|
+
}
|
|
614
745
|
|
|
615
|
-
|
|
616
|
-
|
|
746
|
+
// look for trailing whitespaces from previously processed text
|
|
747
|
+
// which may not be trimmed due to a following comment or an empty
|
|
748
|
+
// element which has now been removed
|
|
749
|
+
function squashTrailingWhitespace(nextTag) {
|
|
750
|
+
var charsIndex;
|
|
751
|
+
if (buffer.length > 1 && /^(?:<!|$)/.test(buffer[buffer.length - 1]) &&
|
|
752
|
+
/\s$/.test(buffer[buffer.length - 2])) {
|
|
753
|
+
charsIndex = buffer.length - 2;
|
|
617
754
|
}
|
|
755
|
+
else if (buffer.length > 0 && /\s$/.test(buffer[buffer.length - 1])) {
|
|
756
|
+
charsIndex = buffer.length - 1;
|
|
757
|
+
}
|
|
758
|
+
if (charsIndex > 0) {
|
|
759
|
+
buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(text) {
|
|
760
|
+
return collapseWhitespaceSmart(text, 'comment', nextTag, options);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
618
764
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
start: function(tag, attrs, unary, unarySlash) {
|
|
765
|
+
new HTMLParser(value, {
|
|
766
|
+
partialMarkup: partialMarkup,
|
|
767
|
+
html5: typeof options.html5 !== 'undefined' ? options.html5 : true,
|
|
623
768
|
|
|
624
|
-
|
|
769
|
+
start: function(tag, attrs, unary, unarySlash, autoGenerated) {
|
|
770
|
+
var lowerTag = tag.toLowerCase();
|
|
625
771
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
nextOptions.keepClosingSlash = true;
|
|
633
|
-
nextOptions.caseSensitive = true;
|
|
634
|
-
options = nextOptions;
|
|
772
|
+
if (lowerTag === 'svg') {
|
|
773
|
+
optionsStack.push(options);
|
|
774
|
+
var nextOptions = {};
|
|
775
|
+
for (var key in options) {
|
|
776
|
+
nextOptions[key] = options[key];
|
|
635
777
|
}
|
|
778
|
+
nextOptions.keepClosingSlash = true;
|
|
779
|
+
nextOptions.caseSensitive = true;
|
|
780
|
+
options = nextOptions;
|
|
781
|
+
}
|
|
636
782
|
|
|
637
|
-
|
|
783
|
+
tag = options.caseSensitive ? tag : lowerTag;
|
|
638
784
|
|
|
639
|
-
|
|
785
|
+
currentTag = tag;
|
|
786
|
+
charsPrevTag = tag;
|
|
787
|
+
if (!inlineTextTags(tag)) {
|
|
640
788
|
currentChars = '';
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
789
|
+
}
|
|
790
|
+
hasChars = false;
|
|
791
|
+
currentAttrs = attrs;
|
|
792
|
+
|
|
793
|
+
var optional = options.removeOptionalTags;
|
|
794
|
+
if (optional) {
|
|
795
|
+
var htmlTag = htmlTags(tag);
|
|
796
|
+
// <html> may be omitted if first thing inside is not comment
|
|
797
|
+
// <head> may be omitted if first thing inside is an element
|
|
798
|
+
// <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
|
|
799
|
+
// <colgroup> may be omitted if first thing inside is <col>
|
|
800
|
+
// <tbody> may be omitted if first thing inside is <tr>
|
|
801
|
+
if (htmlTag && canRemoveParentTag(optionalStartTag, tag)) {
|
|
802
|
+
removeStartTag();
|
|
803
|
+
}
|
|
804
|
+
optionalStartTag = '';
|
|
805
|
+
// end-tag-followed-by-start-tag omission rules
|
|
806
|
+
if (htmlTag && canRemovePrecedingTag(optionalEndTag, tag)) {
|
|
807
|
+
removeEndTag();
|
|
808
|
+
// <colgroup> cannot be omitted if preceding </colgroup> is omitted
|
|
809
|
+
// <tbody> cannot be omitted if preceding </tbody>, </thead> or </tfoot> is omitted
|
|
810
|
+
optional = !isStartTagMandatory(optionalEndTag, tag);
|
|
651
811
|
}
|
|
812
|
+
optionalEndTag = '';
|
|
813
|
+
}
|
|
652
814
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
|
|
815
|
+
// set whitespace flags for nested tags (eg. <code> within a <pre>)
|
|
816
|
+
if (options.collapseWhitespace) {
|
|
817
|
+
if (!stackNoTrimWhitespace.length) {
|
|
818
|
+
squashTrailingWhitespace(tag);
|
|
819
|
+
}
|
|
820
|
+
if (!_canTrimWhitespace(tag, attrs)) {
|
|
821
|
+
stackNoTrimWhitespace.push(tag);
|
|
822
|
+
}
|
|
823
|
+
if (!_canCollapseWhitespace(tag, attrs)) {
|
|
824
|
+
stackNoCollapseWhitespace.push(tag);
|
|
657
825
|
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
var openTag = '<' + tag;
|
|
829
|
+
var hasUnarySlash = unarySlash && options.keepClosingSlash;
|
|
658
830
|
|
|
659
|
-
|
|
831
|
+
buffer.push(openTag);
|
|
832
|
+
|
|
833
|
+
if (lint) {
|
|
834
|
+
lint.testElement(tag);
|
|
835
|
+
}
|
|
660
836
|
|
|
837
|
+
var parts = [ ];
|
|
838
|
+
var token, isLast = true;
|
|
839
|
+
for (var i = attrs.length; --i >= 0; ) {
|
|
661
840
|
if (lint) {
|
|
662
|
-
lint.
|
|
841
|
+
lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].value);
|
|
663
842
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (lint) {
|
|
669
|
-
lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].escaped);
|
|
670
|
-
}
|
|
671
|
-
token = normalizeAttribute(attrs[i], attrs, tag, unarySlash, i, options, isLast);
|
|
672
|
-
if (isLast) {
|
|
673
|
-
token += closeTag;
|
|
674
|
-
}
|
|
675
|
-
buffer.push(token);
|
|
843
|
+
token = normalizeAttribute(attrs[i], attrs, tag, hasUnarySlash, i, options, isLast);
|
|
844
|
+
if (token) {
|
|
845
|
+
isLast = false;
|
|
846
|
+
parts.unshift(token);
|
|
676
847
|
}
|
|
677
|
-
}
|
|
678
|
-
|
|
848
|
+
}
|
|
849
|
+
if (parts.length > 0) {
|
|
850
|
+
buffer.push(' ');
|
|
851
|
+
buffer.push.apply(buffer, parts);
|
|
852
|
+
}
|
|
853
|
+
// start tag must never be omitted if it has any attributes
|
|
854
|
+
else if (optional && optionalStartTags(tag)) {
|
|
855
|
+
optionalStartTag = tag;
|
|
856
|
+
}
|
|
679
857
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
858
|
+
buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
|
|
859
|
+
|
|
860
|
+
if (autoGenerated && !options.includeAutoGeneratedTags) {
|
|
861
|
+
removeStartTag();
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
end: function(tag, attrs, autoGenerated) {
|
|
865
|
+
var lowerTag = tag.toLowerCase();
|
|
866
|
+
if (lowerTag === 'svg') {
|
|
867
|
+
options = optionsStack.pop();
|
|
868
|
+
}
|
|
869
|
+
tag = options.caseSensitive ? tag : lowerTag;
|
|
684
870
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
871
|
+
// check if current tag is in a whitespace stack
|
|
872
|
+
if (options.collapseWhitespace) {
|
|
873
|
+
if (stackNoTrimWhitespace.length) {
|
|
874
|
+
if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
|
|
689
875
|
stackNoTrimWhitespace.pop();
|
|
690
876
|
}
|
|
691
|
-
if (stackNoCollapseWhitespace.length &&
|
|
692
|
-
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
693
|
-
stackNoCollapseWhitespace.pop();
|
|
694
|
-
}
|
|
695
877
|
}
|
|
878
|
+
else {
|
|
879
|
+
squashTrailingWhitespace('/' + tag);
|
|
880
|
+
}
|
|
881
|
+
if (stackNoCollapseWhitespace.length &&
|
|
882
|
+
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
883
|
+
stackNoCollapseWhitespace.pop();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
696
886
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
887
|
+
var isElementEmpty = false;
|
|
888
|
+
if (tag === currentTag) {
|
|
889
|
+
currentTag = '';
|
|
890
|
+
isElementEmpty = !hasChars;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (options.removeOptionalTags) {
|
|
894
|
+
// <html>, <head> or <body> may be omitted if the element is empty
|
|
895
|
+
if (isElementEmpty && topLevelTags(optionalStartTag)) {
|
|
896
|
+
removeStartTag();
|
|
707
897
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
898
|
+
optionalStartTag = '';
|
|
899
|
+
// </html> or </body> may be omitted if not followed by comment
|
|
900
|
+
// </head> may be omitted if not followed by space or comment
|
|
901
|
+
// </p> may be omitted if no more content in non-</a> parent
|
|
902
|
+
// except for </dt> or </thead>, end tags may be omitted if no more content in parent element
|
|
903
|
+
if (htmlTags(tag) && optionalEndTag && !trailingTags(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags(tag))) {
|
|
904
|
+
removeEndTag();
|
|
711
905
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
906
|
+
optionalEndTag = optionalEndTags(tag) ? tag : '';
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
|
|
910
|
+
// remove last "element" from buffer
|
|
911
|
+
removeStartTag();
|
|
912
|
+
optionalStartTag = '';
|
|
913
|
+
optionalEndTag = '';
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
if (options.includeAutoGeneratedTags || !autoGenerated) {
|
|
917
|
+
buffer.push('</' + tag + '>');
|
|
716
918
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
919
|
+
charsPrevTag = '/' + tag;
|
|
920
|
+
if (!inlineTags(tag)) {
|
|
921
|
+
currentChars = '';
|
|
922
|
+
}
|
|
923
|
+
else if (isElementEmpty) {
|
|
924
|
+
currentChars += '|';
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
chars: function(text, prevTag, nextTag) {
|
|
929
|
+
prevTag = prevTag === '' ? 'comment' : prevTag;
|
|
930
|
+
nextTag = nextTag === '' ? 'comment' : nextTag;
|
|
931
|
+
if (options.collapseWhitespace) {
|
|
932
|
+
if (!stackNoTrimWhitespace.length) {
|
|
933
|
+
if (prevTag === 'comment') {
|
|
934
|
+
var removed = buffer[buffer.length - 1] === '';
|
|
935
|
+
if (removed) {
|
|
936
|
+
prevTag = charsPrevTag;
|
|
937
|
+
}
|
|
938
|
+
if (buffer.length > 1 && (removed || / $/.test(currentChars))) {
|
|
939
|
+
var charsIndex = buffer.length - 2;
|
|
940
|
+
buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(trailingSpaces) {
|
|
941
|
+
text = trailingSpaces + text;
|
|
942
|
+
return '';
|
|
943
|
+
});
|
|
944
|
+
}
|
|
728
945
|
}
|
|
729
|
-
if (
|
|
730
|
-
text =
|
|
946
|
+
if (prevTag && inlineTextTags(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
|
|
947
|
+
text = collapseWhitespace(text, options, /(?:^|\s)$/.test(currentChars));
|
|
731
948
|
}
|
|
732
|
-
|
|
733
|
-
|
|
949
|
+
text = prevTag || nextTag ? collapseWhitespaceSmart(text, prevTag, nextTag, options) : trimWhitespace(text);
|
|
950
|
+
if (!text && /\s$/.test(currentChars) && prevTag && prevTag.charAt(0) === '/') {
|
|
951
|
+
for (var index = buffer.length - 2, endTag = prevTag.slice(1); index >= 0 && _canTrimWhitespace(endTag); index--) {
|
|
952
|
+
var str = buffer[index];
|
|
953
|
+
var match = str.match(/^<\/([\w:-]+)>$/);
|
|
954
|
+
if (match) {
|
|
955
|
+
endTag = match[1];
|
|
956
|
+
}
|
|
957
|
+
else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
734
961
|
}
|
|
735
962
|
}
|
|
736
|
-
if (
|
|
737
|
-
text =
|
|
963
|
+
if (!stackNoCollapseWhitespace.length) {
|
|
964
|
+
text = prevTag && nextTag || nextTag === 'html' ? text : collapseWhitespaceAll(text);
|
|
738
965
|
}
|
|
739
|
-
|
|
740
|
-
|
|
966
|
+
}
|
|
967
|
+
if (options.processScripts && (currentTag === 'script' || currentTag === 'style')) {
|
|
968
|
+
text = processScript(text, options, currentAttrs);
|
|
969
|
+
}
|
|
970
|
+
if (options.minifyJS && isExecutableScript(currentTag, currentAttrs)) {
|
|
971
|
+
text = minifyJS(text, options.minifyJS);
|
|
972
|
+
if (text.charAt(text.length - 1) === ';') {
|
|
973
|
+
text = text.slice(0, -1);
|
|
741
974
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
975
|
+
}
|
|
976
|
+
if (options.minifyCSS && isStyleSheet(currentTag, currentAttrs)) {
|
|
977
|
+
text = minifyCSS(text, options.minifyCSS);
|
|
978
|
+
}
|
|
979
|
+
if (options.removeOptionalTags && text) {
|
|
980
|
+
// <html> may be omitted if first thing inside is not comment
|
|
981
|
+
// <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
|
|
982
|
+
if (optionalStartTag === 'html' || optionalStartTag === 'body' && !/^\s/.test(text)) {
|
|
983
|
+
removeStartTag();
|
|
751
984
|
}
|
|
752
|
-
|
|
753
|
-
if
|
|
754
|
-
|
|
985
|
+
optionalStartTag = '';
|
|
986
|
+
// </html> or </body> may be omitted if not followed by comment
|
|
987
|
+
// </head>, </colgroup> or </caption> may be omitted if not followed by space or comment
|
|
988
|
+
if (compactTags(optionalEndTag) || looseTags(optionalEndTag) && !/^\s/.test(text)) {
|
|
989
|
+
removeEndTag();
|
|
755
990
|
}
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
991
|
+
optionalEndTag = '';
|
|
992
|
+
}
|
|
993
|
+
charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
|
|
994
|
+
currentChars += text;
|
|
995
|
+
if (text) {
|
|
996
|
+
hasChars = true;
|
|
997
|
+
}
|
|
998
|
+
if (lint) {
|
|
999
|
+
lint.testChars(text);
|
|
1000
|
+
}
|
|
1001
|
+
buffer.push(text);
|
|
1002
|
+
},
|
|
1003
|
+
comment: function(text, nonStandard) {
|
|
1004
|
+
var prefix = nonStandard ? '<!' : '<!--';
|
|
1005
|
+
var suffix = nonStandard ? '>' : '-->';
|
|
1006
|
+
if (isConditionalComment(text)) {
|
|
1007
|
+
text = prefix + cleanConditionalComment(text, options) + suffix;
|
|
1008
|
+
}
|
|
1009
|
+
else if (options.removeComments) {
|
|
1010
|
+
if (isIgnoredComment(text, options)) {
|
|
1011
|
+
text = '<!--' + text + '-->';
|
|
773
1012
|
}
|
|
774
1013
|
else {
|
|
775
|
-
text =
|
|
1014
|
+
text = '';
|
|
776
1015
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
text = prefix + text + suffix;
|
|
1019
|
+
}
|
|
1020
|
+
if (options.removeOptionalTags && text) {
|
|
1021
|
+
// preceding comments suppress tag omissions
|
|
1022
|
+
optionalStartTag = '';
|
|
1023
|
+
optionalEndTag = '';
|
|
1024
|
+
}
|
|
1025
|
+
buffer.push(text);
|
|
1026
|
+
},
|
|
1027
|
+
doctype: function(doctype) {
|
|
1028
|
+
buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespaceAll(doctype));
|
|
1029
|
+
},
|
|
1030
|
+
customAttrAssign: options.customAttrAssign,
|
|
1031
|
+
customAttrSurround: options.customAttrSurround
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
if (options.removeOptionalTags) {
|
|
1035
|
+
// <html> may be omitted if first thing inside is not comment
|
|
1036
|
+
// <head> or <body> may be omitted if empty
|
|
1037
|
+
if (topLevelTags(optionalStartTag)) {
|
|
1038
|
+
removeStartTag();
|
|
1039
|
+
}
|
|
1040
|
+
// except for </dt> or </thead>, end tags may be omitted if no more content in parent element
|
|
1041
|
+
if (optionalEndTag && !trailingTags(optionalEndTag)) {
|
|
1042
|
+
removeEndTag();
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
788
1045
|
|
|
789
|
-
|
|
1046
|
+
var str = joinResultSegments(buffer, options);
|
|
790
1047
|
|
|
791
|
-
|
|
792
|
-
|
|
1048
|
+
if (uidAttr) {
|
|
1049
|
+
str = str.replace(new RegExp('(\\s*)' + uidAttr + '([0-9]+)(\\s*)', 'g'), function(match, prefix, index, suffix) {
|
|
1050
|
+
var chunk = ignoredCustomMarkupChunks[+index];
|
|
1051
|
+
if (options.collapseWhitespace) {
|
|
1052
|
+
if (prefix !== '\t') {
|
|
1053
|
+
chunk = prefix + chunk;
|
|
1054
|
+
}
|
|
1055
|
+
if (suffix !== '\t') {
|
|
1056
|
+
chunk += suffix;
|
|
1057
|
+
}
|
|
1058
|
+
return collapseWhitespace(chunk, {
|
|
1059
|
+
preserveLineBreaks: options.preserveLineBreaks,
|
|
1060
|
+
conservativeCollapse: true
|
|
1061
|
+
}, /^\s/.test(chunk), /\s$/.test(chunk));
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
return chunk;
|
|
1065
|
+
}
|
|
793
1066
|
});
|
|
794
|
-
|
|
795
|
-
|
|
1067
|
+
}
|
|
1068
|
+
if (uidIgnore) {
|
|
1069
|
+
str = str.replace(new RegExp('<!--!' + uidIgnore + '([0-9]+)-->', 'g'), function(match, index) {
|
|
1070
|
+
return ignoredMarkupChunks[+index];
|
|
796
1071
|
});
|
|
797
|
-
|
|
798
|
-
log('minified in: ' + (new Date() - t) + 'ms');
|
|
799
|
-
return str;
|
|
800
1072
|
}
|
|
801
1073
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1074
|
+
log('minified in: ' + (Date.now() - t) + 'ms');
|
|
1075
|
+
return str;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function joinResultSegments(results, options) {
|
|
1079
|
+
var str;
|
|
1080
|
+
var maxLineLength = options.maxLineLength;
|
|
1081
|
+
if (maxLineLength) {
|
|
1082
|
+
var token;
|
|
1083
|
+
var lines = [];
|
|
1084
|
+
var line = '';
|
|
1085
|
+
for (var i = 0, len = results.length; i < len; i++) {
|
|
1086
|
+
token = results[i];
|
|
1087
|
+
if (line.length + token.length < maxLineLength) {
|
|
1088
|
+
line += token;
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
lines.push(line.replace(/^\n/, ''));
|
|
1092
|
+
line = token;
|
|
818
1093
|
}
|
|
819
|
-
lines.push(line);
|
|
820
|
-
|
|
821
|
-
str = lines.join('\n');
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
str = results.join('');
|
|
825
1094
|
}
|
|
1095
|
+
lines.push(line);
|
|
826
1096
|
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// for CommonJS enviroments, export everything
|
|
831
|
-
if (typeof exports !== 'undefined') {
|
|
832
|
-
exports.minify = minify;
|
|
1097
|
+
str = lines.join('\n');
|
|
833
1098
|
}
|
|
834
1099
|
else {
|
|
835
|
-
|
|
1100
|
+
str = results.join('');
|
|
836
1101
|
}
|
|
837
1102
|
|
|
838
|
-
|
|
1103
|
+
return options.collapseWhitespace ? trimWhitespace(str) : str;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
exports.minify = function(value, options) {
|
|
1107
|
+
return minify(value, options);
|
|
1108
|
+
};
|