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