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