html-minifier-next 1.0.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.
@@ -0,0 +1,1858 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var CleanCSS = require('clean-css');
6
+ var entities = require('entities');
7
+ var RelateURL = require('relateurl');
8
+ var terser = require('terser');
9
+
10
+ async function replaceAsync(str, regex, asyncFn) {
11
+ const promises = [];
12
+
13
+ str.replace(regex, (match, ...args) => {
14
+ const promise = asyncFn(match, ...args);
15
+ promises.push(promise);
16
+ });
17
+
18
+ const data = await Promise.all(promises);
19
+ return str.replace(regex, () => data.shift());
20
+ }
21
+
22
+ /*!
23
+ * HTML Parser By John Resig (ejohn.org)
24
+ * Modified by Juriy "kangax" Zaytsev
25
+ * Original code by Erik Arvidsson, Mozilla Public License
26
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
27
+ */
28
+
29
+
30
+ class CaseInsensitiveSet extends Set {
31
+ has(str) {
32
+ return super.has(str.toLowerCase());
33
+ }
34
+ }
35
+
36
+ // Regular Expressions for parsing tags and attributes
37
+ const singleAttrIdentifier = /([^\s"'<>/=]+)/;
38
+ const singleAttrAssigns = [/=/];
39
+ const singleAttrValues = [
40
+ // attr value double quotes
41
+ /"([^"]*)"+/.source,
42
+ // attr value, single quotes
43
+ /'([^']*)'+/.source,
44
+ // attr value, no quotes
45
+ /([^ \t\n\f\r"'`=<>]+)/.source
46
+ ];
47
+ // https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
48
+ const qnameCapture = (function () {
49
+ // based on https://www.npmjs.com/package/ncname
50
+ const combiningChar = '\\u0300-\\u0345\\u0360\\u0361\\u0483-\\u0486\\u0591-\\u05A1\\u05A3-\\u05B9\\u05BB-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u064B-\\u0652\\u0670\\u06D6-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0901-\\u0903\\u093C\\u093E-\\u094D\\u0951-\\u0954\\u0962\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2\\u09E3\\u0A02\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A70\\u0A71\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B43\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B56\\u0B57\\u0B82\\u0B83\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C01-\\u0C03\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C82\\u0C83\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D43\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E\\u0F3F\\u0F71-\\u0F84\\u0F86-\\u0F8B\\u0F90-\\u0F95\\u0F97\\u0F99-\\u0FAD\\u0FB1-\\u0FB7\\u0FB9\\u20D0-\\u20DC\\u20E1\\u302A-\\u302F\\u3099\\u309A';
51
+ const digit = '0-9\\u0660-\\u0669\\u06F0-\\u06F9\\u0966-\\u096F\\u09E6-\\u09EF\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0BE7-\\u0BEF\\u0C66-\\u0C6F\\u0CE6-\\u0CEF\\u0D66-\\u0D6F\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F29';
52
+ const extender = '\\xB7\\u02D0\\u02D1\\u0387\\u0640\\u0E46\\u0EC6\\u3005\\u3031-\\u3035\\u309D\\u309E\\u30FC-\\u30FE';
53
+ const letter = 'A-Za-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u0131\\u0134-\\u013E\\u0141-\\u0148\\u014A-\\u017E\\u0180-\\u01C3\\u01CD-\\u01F0\\u01F4\\u01F5\\u01FA-\\u0217\\u0250-\\u02A8\\u02BB-\\u02C1\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03CE\\u03D0-\\u03D6\\u03DA\\u03DC\\u03DE\\u03E0\\u03E2-\\u03F3\\u0401-\\u040C\\u040E-\\u044F\\u0451-\\u045C\\u045E-\\u0481\\u0490-\\u04C4\\u04C7\\u04C8\\u04CB\\u04CC\\u04D0-\\u04EB\\u04EE-\\u04F5\\u04F8\\u04F9\\u0531-\\u0556\\u0559\\u0561-\\u0586\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u063A\\u0641-\\u064A\\u0671-\\u06B7\\u06BA-\\u06BE\\u06C0-\\u06CE\\u06D0-\\u06D3\\u06D5\\u06E5\\u06E6\\u0905-\\u0939\\u093D\\u0958-\\u0961\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8B\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AE0\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B36-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB5\\u0BB7-\\u0BB9\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D60\\u0D61\\u0E01-\\u0E2E\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E45\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD\\u0EAE\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0F40-\\u0F47\\u0F49-\\u0F69\\u10A0-\\u10C5\\u10D0-\\u10F6\\u1100\\u1102\\u1103\\u1105-\\u1107\\u1109\\u110B\\u110C\\u110E-\\u1112\\u113C\\u113E\\u1140\\u114C\\u114E\\u1150\\u1154\\u1155\\u1159\\u115F-\\u1161\\u1163\\u1165\\u1167\\u1169\\u116D\\u116E\\u1172\\u1173\\u1175\\u119E\\u11A8\\u11AB\\u11AE\\u11AF\\u11B7\\u11B8\\u11BA\\u11BC-\\u11C2\\u11EB\\u11F0\\u11F9\\u1E00-\\u1E9B\\u1EA0-\\u1EF9\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2126\\u212A\\u212B\\u212E\\u2180-\\u2182\\u3007\\u3021-\\u3029\\u3041-\\u3094\\u30A1-\\u30FA\\u3105-\\u312C\\u4E00-\\u9FA5\\uAC00-\\uD7A3';
54
+ const ncname = '[' + letter + '_][' + letter + digit + '\\.\\-_' + combiningChar + extender + ']*';
55
+ return '((?:' + ncname + '\\:)?' + ncname + ')';
56
+ })();
57
+ const startTagOpen = new RegExp('^<' + qnameCapture);
58
+ const startTagClose = /^\s*(\/?)>/;
59
+ const endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>');
60
+ const doctype = /^<!DOCTYPE\s?[^>]+>/i;
61
+
62
+ let IS_REGEX_CAPTURING_BROKEN = false;
63
+ 'x'.replace(/x(.)?/g, function (m, g) {
64
+ IS_REGEX_CAPTURING_BROKEN = g === '';
65
+ });
66
+
67
+ // Empty Elements
68
+ const empty = new CaseInsensitiveSet(['area', 'base', 'basefont', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
69
+
70
+ // Inline Elements
71
+ const inline = new CaseInsensitiveSet(['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'noscript', 'object', 'q', 's', 'samp', 'script', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'tt', 'u', 'var']);
72
+
73
+ // Elements that you can, intentionally, leave open
74
+ // (and which close themselves)
75
+ const closeSelf = new CaseInsensitiveSet(['colgroup', 'dd', 'dt', 'li', 'option', 'p', 'td', 'tfoot', 'th', 'thead', 'tr', 'source']);
76
+
77
+ // Attributes that have their values filled in disabled='disabled'
78
+ const fillAttrs = new CaseInsensitiveSet(['checked', 'compact', 'declare', 'defer', 'disabled', 'ismap', 'multiple', 'nohref', 'noresize', 'noshade', 'nowrap', 'readonly', 'selected']);
79
+
80
+ // Special Elements (can contain anything)
81
+ const special = new CaseInsensitiveSet(['script', 'style']);
82
+
83
+ // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
84
+ // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
85
+ const nonPhrasing = new CaseInsensitiveSet(['address', 'article', 'aside', 'base', 'blockquote', 'body', 'caption', 'col', 'colgroup', 'dd', 'details', 'dialog', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'legend', 'li', 'menuitem', 'meta', 'ol', 'optgroup', 'option', 'param', 'rp', 'rt', 'source', 'style', 'summary', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul']);
86
+
87
+ const reCache = {};
88
+
89
+ function attrForHandler(handler) {
90
+ let pattern = singleAttrIdentifier.source +
91
+ '(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' +
92
+ '[ \\t\\n\\f\\r]*(?:' + singleAttrValues.join('|') + '))?';
93
+ if (handler.customAttrSurround) {
94
+ const attrClauses = [];
95
+ for (let i = handler.customAttrSurround.length - 1; i >= 0; i--) {
96
+ attrClauses[i] = '(?:' +
97
+ '(' + handler.customAttrSurround[i][0].source + ')\\s*' +
98
+ pattern +
99
+ '\\s*(' + handler.customAttrSurround[i][1].source + ')' +
100
+ ')';
101
+ }
102
+ attrClauses.push('(?:' + pattern + ')');
103
+ pattern = '(?:' + attrClauses.join('|') + ')';
104
+ }
105
+ return new RegExp('^\\s*' + pattern);
106
+ }
107
+
108
+ function joinSingleAttrAssigns(handler) {
109
+ return singleAttrAssigns.concat(
110
+ handler.customAttrAssign || []
111
+ ).map(function (assign) {
112
+ return '(?:' + assign.source + ')';
113
+ }).join('|');
114
+ }
115
+
116
+ class HTMLParser {
117
+ constructor(html, handler) {
118
+ this.html = html;
119
+ this.handler = handler;
120
+ }
121
+
122
+ async parse() {
123
+ let html = this.html;
124
+ const handler = this.handler;
125
+
126
+ const stack = []; let lastTag;
127
+ const attribute = attrForHandler(handler);
128
+ let last, prevTag, nextTag;
129
+ while (html) {
130
+ last = html;
131
+ // Make sure we're not in a script or style element
132
+ if (!lastTag || !special.has(lastTag)) {
133
+ let textEnd = html.indexOf('<');
134
+ if (textEnd === 0) {
135
+ // Comment:
136
+ if (/^<!--/.test(html)) {
137
+ const commentEnd = html.indexOf('-->');
138
+
139
+ if (commentEnd >= 0) {
140
+ if (handler.comment) {
141
+ await handler.comment(html.substring(4, commentEnd));
142
+ }
143
+ html = html.substring(commentEnd + 3);
144
+ prevTag = '';
145
+ continue;
146
+ }
147
+ }
148
+
149
+ // https://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
150
+ if (/^<!\[/.test(html)) {
151
+ const conditionalEnd = html.indexOf(']>');
152
+
153
+ if (conditionalEnd >= 0) {
154
+ if (handler.comment) {
155
+ await handler.comment(html.substring(2, conditionalEnd + 1), true /* non-standard */);
156
+ }
157
+ html = html.substring(conditionalEnd + 2);
158
+ prevTag = '';
159
+ continue;
160
+ }
161
+ }
162
+
163
+ // Doctype:
164
+ const doctypeMatch = html.match(doctype);
165
+ if (doctypeMatch) {
166
+ if (handler.doctype) {
167
+ handler.doctype(doctypeMatch[0]);
168
+ }
169
+ html = html.substring(doctypeMatch[0].length);
170
+ prevTag = '';
171
+ continue;
172
+ }
173
+
174
+ // End tag:
175
+ const endTagMatch = html.match(endTag);
176
+ if (endTagMatch) {
177
+ html = html.substring(endTagMatch[0].length);
178
+ await replaceAsync(endTagMatch[0], endTag, parseEndTag);
179
+ prevTag = '/' + endTagMatch[1].toLowerCase();
180
+ continue;
181
+ }
182
+
183
+ // Start tag:
184
+ const startTagMatch = parseStartTag(html);
185
+ if (startTagMatch) {
186
+ html = startTagMatch.rest;
187
+ await handleStartTag(startTagMatch);
188
+ prevTag = startTagMatch.tagName.toLowerCase();
189
+ continue;
190
+ }
191
+
192
+ // Treat `<` as text
193
+ if (handler.continueOnParseError) {
194
+ textEnd = html.indexOf('<', 1);
195
+ }
196
+ }
197
+
198
+ let text;
199
+ if (textEnd >= 0) {
200
+ text = html.substring(0, textEnd);
201
+ html = html.substring(textEnd);
202
+ } else {
203
+ text = html;
204
+ html = '';
205
+ }
206
+
207
+ // next tag
208
+ let nextTagMatch = parseStartTag(html);
209
+ if (nextTagMatch) {
210
+ nextTag = nextTagMatch.tagName;
211
+ } else {
212
+ nextTagMatch = html.match(endTag);
213
+ if (nextTagMatch) {
214
+ nextTag = '/' + nextTagMatch[1];
215
+ } else {
216
+ nextTag = '';
217
+ }
218
+ }
219
+
220
+ if (handler.chars) {
221
+ await handler.chars(text, prevTag, nextTag);
222
+ }
223
+ prevTag = '';
224
+ } else {
225
+ const stackedTag = lastTag.toLowerCase();
226
+ const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)</' + stackedTag + '[^>]*>', 'i'));
227
+
228
+ html = await replaceAsync(html, reStackedTag, async (_, text) => {
229
+ if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
230
+ text = text
231
+ .replace(/<!--([\s\S]*?)-->/g, '$1')
232
+ .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
233
+ }
234
+
235
+ if (handler.chars) {
236
+ await handler.chars(text);
237
+ }
238
+
239
+ return '';
240
+ });
241
+
242
+ await parseEndTag('</' + stackedTag + '>', stackedTag);
243
+ }
244
+
245
+ if (html === last) {
246
+ throw new Error('Parse Error: ' + html);
247
+ }
248
+ }
249
+
250
+ if (!handler.partialMarkup) {
251
+ // Clean up any remaining tags
252
+ await parseEndTag();
253
+ }
254
+
255
+ function parseStartTag(input) {
256
+ const start = input.match(startTagOpen);
257
+ if (start) {
258
+ const match = {
259
+ tagName: start[1],
260
+ attrs: []
261
+ };
262
+ input = input.slice(start[0].length);
263
+ let end, attr;
264
+ while (!(end = input.match(startTagClose)) && (attr = input.match(attribute))) {
265
+ input = input.slice(attr[0].length);
266
+ match.attrs.push(attr);
267
+ }
268
+ if (end) {
269
+ match.unarySlash = end[1];
270
+ match.rest = input.slice(end[0].length);
271
+ return match;
272
+ }
273
+ }
274
+ }
275
+
276
+ async function closeIfFound(tagName) {
277
+ if (findTag(tagName) >= 0) {
278
+ await parseEndTag('', tagName);
279
+ return true;
280
+ }
281
+ }
282
+
283
+ async function handleStartTag(match) {
284
+ const tagName = match.tagName;
285
+ let unarySlash = match.unarySlash;
286
+
287
+ if (handler.html5) {
288
+ if (lastTag === 'p' && nonPhrasing.has(tagName)) {
289
+ await parseEndTag('', lastTag);
290
+ } else if (tagName === 'tbody') {
291
+ await closeIfFound('thead');
292
+ } else if (tagName === 'tfoot') {
293
+ if (!await closeIfFound('tbody')) {
294
+ await closeIfFound('thead');
295
+ }
296
+ }
297
+ if (tagName === 'col' && findTag('colgroup') < 0) {
298
+ lastTag = 'colgroup';
299
+ stack.push({ tag: lastTag, attrs: [] });
300
+ if (handler.start) {
301
+ await handler.start(lastTag, [], false, '');
302
+ }
303
+ }
304
+ }
305
+
306
+ if (!handler.html5 && !inline.has(tagName)) {
307
+ while (lastTag && inline.has(lastTag)) {
308
+ await parseEndTag('', lastTag);
309
+ }
310
+ }
311
+
312
+ if (closeSelf.has(tagName) && lastTag === tagName) {
313
+ await parseEndTag('', tagName);
314
+ }
315
+
316
+ const unary = empty.has(tagName) || (tagName === 'html' && lastTag === 'head') || !!unarySlash;
317
+
318
+ const attrs = match.attrs.map(function (args) {
319
+ let name, value, customOpen, customClose, customAssign, quote;
320
+ const ncp = 7; // number of captured parts, scalar
321
+
322
+ // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
323
+ if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
324
+ if (args[3] === '') { delete args[3]; }
325
+ if (args[4] === '') { delete args[4]; }
326
+ if (args[5] === '') { delete args[5]; }
327
+ }
328
+
329
+ function populate(index) {
330
+ customAssign = args[index];
331
+ value = args[index + 1];
332
+ if (typeof value !== 'undefined') {
333
+ return '"';
334
+ }
335
+ value = args[index + 2];
336
+ if (typeof value !== 'undefined') {
337
+ return '\'';
338
+ }
339
+ value = args[index + 3];
340
+ if (typeof value === 'undefined' && fillAttrs.has(name)) {
341
+ value = name;
342
+ }
343
+ return '';
344
+ }
345
+
346
+ let j = 1;
347
+ if (handler.customAttrSurround) {
348
+ for (let i = 0, l = handler.customAttrSurround.length; i < l; i++, j += ncp) {
349
+ name = args[j + 1];
350
+ if (name) {
351
+ quote = populate(j + 2);
352
+ customOpen = args[j];
353
+ customClose = args[j + 6];
354
+ break;
355
+ }
356
+ }
357
+ }
358
+
359
+ if (!name && (name = args[j])) {
360
+ quote = populate(j + 1);
361
+ }
362
+
363
+ return {
364
+ name,
365
+ value,
366
+ customAssign: customAssign || '=',
367
+ customOpen: customOpen || '',
368
+ customClose: customClose || '',
369
+ quote: quote || ''
370
+ };
371
+ });
372
+
373
+ if (!unary) {
374
+ stack.push({ tag: tagName, attrs });
375
+ lastTag = tagName;
376
+ unarySlash = '';
377
+ }
378
+
379
+ if (handler.start) {
380
+ await handler.start(tagName, attrs, unary, unarySlash);
381
+ }
382
+ }
383
+
384
+ function findTag(tagName) {
385
+ let pos;
386
+ const needle = tagName.toLowerCase();
387
+ for (pos = stack.length - 1; pos >= 0; pos--) {
388
+ if (stack[pos].tag.toLowerCase() === needle) {
389
+ break;
390
+ }
391
+ }
392
+ return pos;
393
+ }
394
+
395
+ async function parseEndTag(tag, tagName) {
396
+ let pos;
397
+
398
+ // Find the closest opened tag of the same type
399
+ if (tagName) {
400
+ pos = findTag(tagName);
401
+ } else { // If no tag name is provided, clean shop
402
+ pos = 0;
403
+ }
404
+
405
+ if (pos >= 0) {
406
+ // Close all the open elements, up the stack
407
+ for (let i = stack.length - 1; i >= pos; i--) {
408
+ if (handler.end) {
409
+ handler.end(stack[i].tag, stack[i].attrs, i > pos || !tag);
410
+ }
411
+ }
412
+
413
+ // Remove the open elements from the stack
414
+ stack.length = pos;
415
+ lastTag = pos && stack[pos - 1].tag;
416
+ } else if (tagName.toLowerCase() === 'br') {
417
+ if (handler.start) {
418
+ await handler.start(tagName, [], true, '');
419
+ }
420
+ } else if (tagName.toLowerCase() === 'p') {
421
+ if (handler.start) {
422
+ await handler.start(tagName, [], false, '', true);
423
+ }
424
+ if (handler.end) {
425
+ handler.end(tagName, []);
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ class Sorter {
433
+ sort(tokens, fromIndex = 0) {
434
+ for (let i = 0, len = this.keys.length; i < len; i++) {
435
+ const key = this.keys[i];
436
+ const token = key.slice(1);
437
+
438
+ let index = tokens.indexOf(token, fromIndex);
439
+
440
+ if (index !== -1) {
441
+ do {
442
+ if (index !== fromIndex) {
443
+ tokens.splice(index, 1);
444
+ tokens.splice(fromIndex, 0, token);
445
+ }
446
+ fromIndex++;
447
+ } while ((index = tokens.indexOf(token, fromIndex)) !== -1);
448
+
449
+ return this[key].sort(tokens, fromIndex);
450
+ }
451
+ }
452
+ return tokens;
453
+ }
454
+ }
455
+
456
+ class TokenChain {
457
+ add(tokens) {
458
+ tokens.forEach((token) => {
459
+ const key = '$' + token;
460
+ if (!this[key]) {
461
+ this[key] = [];
462
+ this[key].processed = 0;
463
+ }
464
+ this[key].push(tokens);
465
+ });
466
+ }
467
+
468
+ createSorter() {
469
+ const sorter = new Sorter();
470
+
471
+ sorter.keys = Object.keys(this).sort((j, k) => {
472
+ const m = this[j].length;
473
+ const n = this[k].length;
474
+ return m < n ? 1 : m > n ? -1 : j < k ? -1 : j > k ? 1 : 0;
475
+ }).filter((key) => {
476
+ if (this[key].processed < this[key].length) {
477
+ const token = key.slice(1);
478
+ const chain = new TokenChain();
479
+
480
+ this[key].forEach((tokens) => {
481
+ let index;
482
+ while ((index = tokens.indexOf(token)) !== -1) {
483
+ tokens.splice(index, 1);
484
+ }
485
+ tokens.forEach((token) => {
486
+ this['$' + token].processed++;
487
+ });
488
+ chain.add(tokens.slice(0));
489
+ });
490
+ sorter[key] = chain.createSorter();
491
+ return true;
492
+ }
493
+ return false;
494
+ });
495
+ return sorter;
496
+ }
497
+ }
498
+
499
+ function trimWhitespace(str) {
500
+ return str && str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
501
+ }
502
+
503
+ function collapseWhitespaceAll(str) {
504
+ // Non-breaking space is specifically handled inside the replacer function here:
505
+ return str && str.replace(/[ \n\r\t\f\xA0]+/g, function (spaces) {
506
+ return spaces === '\t' ? '\t' : spaces.replace(/(^|\xA0+)[^\xA0]+/g, '$1 ');
507
+ });
508
+ }
509
+
510
+ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
511
+ let lineBreakBefore = ''; let lineBreakAfter = '';
512
+
513
+ if (options.preserveLineBreaks) {
514
+ str = str.replace(/^[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*/, function () {
515
+ lineBreakBefore = '\n';
516
+ return '';
517
+ }).replace(/[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*$/, function () {
518
+ lineBreakAfter = '\n';
519
+ return '';
520
+ });
521
+ }
522
+
523
+ if (trimLeft) {
524
+ // Non-breaking space is specifically handled inside the replacer function here:
525
+ str = str.replace(/^[ \n\r\t\f\xA0]+/, function (spaces) {
526
+ const conservative = !lineBreakBefore && options.conservativeCollapse;
527
+ if (conservative && spaces === '\t') {
528
+ return '\t';
529
+ }
530
+ return spaces.replace(/^[^\xA0]+/, '').replace(/(\xA0+)[^\xA0]+/g, '$1 ') || (conservative ? ' ' : '');
531
+ });
532
+ }
533
+
534
+ if (trimRight) {
535
+ // Non-breaking space is specifically handled inside the replacer function here:
536
+ str = str.replace(/[ \n\r\t\f\xA0]+$/, function (spaces) {
537
+ const conservative = !lineBreakAfter && options.conservativeCollapse;
538
+ if (conservative && spaces === '\t') {
539
+ return '\t';
540
+ }
541
+ return spaces.replace(/[^\xA0]+(\xA0+)/g, ' $1').replace(/[^\xA0]+$/, '') || (conservative ? ' ' : '');
542
+ });
543
+ }
544
+
545
+ if (collapseAll) {
546
+ // strip non space whitespace then compress spaces to one
547
+ str = collapseWhitespaceAll(str);
548
+ }
549
+
550
+ return lineBreakBefore + str + lineBreakAfter;
551
+ }
552
+
553
+ // non-empty tags that will maintain whitespace around them
554
+ const inlineTags = new Set(['a', 'abbr', 'acronym', 'b', 'bdi', 'bdo', 'big', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'label', 'mark', 'math', 'nobr', 'object', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'tt', 'u', 'var']);
555
+ // non-empty tags that will maintain whitespace within them
556
+ const inlineTextTags = new Set(['a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'nobr', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'time', 'tt', 'u', 'var']);
557
+ // self-closing tags that will maintain whitespace around them
558
+ const selfClosingInlineTags = new Set(['comment', 'img', 'input', 'wbr']);
559
+
560
+ function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
561
+ let trimLeft = prevTag && !selfClosingInlineTags.has(prevTag);
562
+ if (trimLeft && !options.collapseInlineTagWhitespace) {
563
+ trimLeft = prevTag.charAt(0) === '/' ? !inlineTags.has(prevTag.slice(1)) : !inlineTextTags.has(prevTag);
564
+ }
565
+ let trimRight = nextTag && !selfClosingInlineTags.has(nextTag);
566
+ if (trimRight && !options.collapseInlineTagWhitespace) {
567
+ trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags.has(nextTag.slice(1)) : !inlineTags.has(nextTag);
568
+ }
569
+ return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
570
+ }
571
+
572
+ function isConditionalComment(text) {
573
+ return /^\[if\s[^\]]+]|\[endif]$/.test(text);
574
+ }
575
+
576
+ function isIgnoredComment(text, options) {
577
+ for (let i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
578
+ if (options.ignoreCustomComments[i].test(text)) {
579
+ return true;
580
+ }
581
+ }
582
+ return false;
583
+ }
584
+
585
+ function isEventAttribute(attrName, options) {
586
+ const patterns = options.customEventAttributes;
587
+ if (patterns) {
588
+ for (let i = patterns.length; i--;) {
589
+ if (patterns[i].test(attrName)) {
590
+ return true;
591
+ }
592
+ }
593
+ return false;
594
+ }
595
+ return /^on[a-z]{3,}$/.test(attrName);
596
+ }
597
+
598
+ function canRemoveAttributeQuotes(value) {
599
+ // https://mathiasbynens.be/notes/unquoted-attribute-values
600
+ return /^[^ \t\n\f\r"'`=<>]+$/.test(value);
601
+ }
602
+
603
+ function attributesInclude(attributes, attribute) {
604
+ for (let i = attributes.length; i--;) {
605
+ if (attributes[i].name.toLowerCase() === attribute) {
606
+ return true;
607
+ }
608
+ }
609
+ return false;
610
+ }
611
+
612
+ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
613
+ attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
614
+
615
+ return (
616
+ (tag === 'script' &&
617
+ attrName === 'language' &&
618
+ attrValue === 'javascript') ||
619
+
620
+ (tag === 'form' &&
621
+ attrName === 'method' &&
622
+ attrValue === 'get') ||
623
+
624
+ (tag === 'input' &&
625
+ attrName === 'type' &&
626
+ attrValue === 'text') ||
627
+
628
+ (tag === 'script' &&
629
+ attrName === 'charset' &&
630
+ !attributesInclude(attrs, 'src')) ||
631
+
632
+ (tag === 'a' &&
633
+ attrName === 'name' &&
634
+ attributesInclude(attrs, 'id')) ||
635
+
636
+ (tag === 'area' &&
637
+ attrName === 'shape' &&
638
+ attrValue === 'rect')
639
+ );
640
+ }
641
+
642
+ // https://mathiasbynens.be/demo/javascript-mime-type
643
+ // https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
644
+ const executableScriptsMimetypes = new Set([
645
+ 'text/javascript',
646
+ 'text/ecmascript',
647
+ 'text/jscript',
648
+ 'application/javascript',
649
+ 'application/x-javascript',
650
+ 'application/ecmascript',
651
+ 'module'
652
+ ]);
653
+
654
+ const keepScriptsMimetypes = new Set([
655
+ 'module'
656
+ ]);
657
+
658
+ function isScriptTypeAttribute(attrValue = '') {
659
+ attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
660
+ return attrValue === '' || executableScriptsMimetypes.has(attrValue);
661
+ }
662
+
663
+ function keepScriptTypeAttribute(attrValue = '') {
664
+ attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
665
+ return keepScriptsMimetypes.has(attrValue);
666
+ }
667
+
668
+ function isExecutableScript(tag, attrs) {
669
+ if (tag !== 'script') {
670
+ return false;
671
+ }
672
+ for (let i = 0, len = attrs.length; i < len; i++) {
673
+ const attrName = attrs[i].name.toLowerCase();
674
+ if (attrName === 'type') {
675
+ return isScriptTypeAttribute(attrs[i].value);
676
+ }
677
+ }
678
+ return true;
679
+ }
680
+
681
+ function isStyleLinkTypeAttribute(attrValue = '') {
682
+ attrValue = trimWhitespace(attrValue).toLowerCase();
683
+ return attrValue === '' || attrValue === 'text/css';
684
+ }
685
+
686
+ function isStyleSheet(tag, attrs) {
687
+ if (tag !== 'style') {
688
+ return false;
689
+ }
690
+ for (let i = 0, len = attrs.length; i < len; i++) {
691
+ const attrName = attrs[i].name.toLowerCase();
692
+ if (attrName === 'type') {
693
+ return isStyleLinkTypeAttribute(attrs[i].value);
694
+ }
695
+ }
696
+ return true;
697
+ }
698
+
699
+ const isSimpleBoolean = new Set(['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']);
700
+ const isBooleanValue = new Set(['true', 'false']);
701
+
702
+ function isBooleanAttribute(attrName, attrValue) {
703
+ return isSimpleBoolean.has(attrName) || (attrName === 'draggable' && !isBooleanValue.has(attrValue));
704
+ }
705
+
706
+ function isUriTypeAttribute(attrName, tag) {
707
+ return (
708
+ (/^(?:a|area|link|base)$/.test(tag) && attrName === 'href') ||
709
+ (tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName)) ||
710
+ (tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName)) ||
711
+ (tag === 'q' && attrName === 'cite') ||
712
+ (tag === 'blockquote' && attrName === 'cite') ||
713
+ ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
714
+ (tag === 'form' && attrName === 'action') ||
715
+ (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
716
+ (tag === 'head' && attrName === 'profile') ||
717
+ (tag === 'script' && (attrName === 'src' || attrName === 'for'))
718
+ );
719
+ }
720
+
721
+ function isNumberTypeAttribute(attrName, tag) {
722
+ return (
723
+ (/^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex') ||
724
+ (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
725
+ (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
726
+ (tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName)) ||
727
+ (tag === 'colgroup' && attrName === 'span') ||
728
+ (tag === 'col' && attrName === 'span') ||
729
+ ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
730
+ );
731
+ }
732
+
733
+ function isLinkType(tag, attrs, value) {
734
+ if (tag !== 'link') {
735
+ return false;
736
+ }
737
+ for (let i = 0, len = attrs.length; i < len; i++) {
738
+ if (attrs[i].name === 'rel' && attrs[i].value === value) {
739
+ return true;
740
+ }
741
+ }
742
+ }
743
+
744
+ function isMediaQuery(tag, attrs, attrName) {
745
+ return attrName === 'media' && (isLinkType(tag, attrs, 'stylesheet') || isStyleSheet(tag, attrs));
746
+ }
747
+
748
+ const srcsetTags = new Set(['img', 'source']);
749
+
750
+ function isSrcset(attrName, tag) {
751
+ return attrName === 'srcset' && srcsetTags.has(tag);
752
+ }
753
+
754
+ async function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
755
+ if (isEventAttribute(attrName, options)) {
756
+ attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
757
+ return options.minifyJS(attrValue, true);
758
+ } else if (attrName === 'class') {
759
+ attrValue = trimWhitespace(attrValue);
760
+ if (options.sortClassName) {
761
+ attrValue = options.sortClassName(attrValue);
762
+ } else {
763
+ attrValue = collapseWhitespaceAll(attrValue);
764
+ }
765
+ return attrValue;
766
+ } else if (isUriTypeAttribute(attrName, tag)) {
767
+ attrValue = trimWhitespace(attrValue);
768
+ return isLinkType(tag, attrs, 'canonical') ? attrValue : options.minifyURLs(attrValue);
769
+ } else if (isNumberTypeAttribute(attrName, tag)) {
770
+ return trimWhitespace(attrValue);
771
+ } else if (attrName === 'style') {
772
+ attrValue = trimWhitespace(attrValue);
773
+ if (attrValue) {
774
+ if (/;$/.test(attrValue) && !/&#?[0-9a-zA-Z]+;$/.test(attrValue)) {
775
+ attrValue = attrValue.replace(/\s*;$/, ';');
776
+ }
777
+ attrValue = await options.minifyCSS(attrValue, 'inline');
778
+ }
779
+ return attrValue;
780
+ } else if (isSrcset(attrName, tag)) {
781
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
782
+ attrValue = trimWhitespace(attrValue).split(/\s+,\s*|\s*,\s+/).map(function (candidate) {
783
+ let url = candidate;
784
+ let descriptor = '';
785
+ const match = candidate.match(/\s+([1-9][0-9]*w|[0-9]+(?:\.[0-9]+)?x)$/);
786
+ if (match) {
787
+ url = url.slice(0, -match[0].length);
788
+ const num = +match[1].slice(0, -1);
789
+ const suffix = match[1].slice(-1);
790
+ if (num !== 1 || suffix !== 'x') {
791
+ descriptor = ' ' + num + suffix;
792
+ }
793
+ }
794
+ return options.minifyURLs(url) + descriptor;
795
+ }).join(', ');
796
+ } else if (isMetaViewport(tag, attrs) && attrName === 'content') {
797
+ attrValue = attrValue.replace(/\s+/g, '').replace(/[0-9]+\.[0-9]+/g, function (numString) {
798
+ // "0.90000" -> "0.9"
799
+ // "1.0" -> "1"
800
+ // "1.0001" -> "1.0001" (unchanged)
801
+ return (+numString).toString();
802
+ });
803
+ } else if (isContentSecurityPolicy(tag, attrs) && attrName.toLowerCase() === 'content') {
804
+ return collapseWhitespaceAll(attrValue);
805
+ } else if (options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
806
+ attrValue = trimWhitespace(attrValue.replace(/ ?[\n\r]+ ?/g, '').replace(/\s{2,}/g, options.conservativeCollapse ? ' ' : ''));
807
+ } else if (tag === 'script' && attrName === 'type') {
808
+ attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
809
+ } else if (isMediaQuery(tag, attrs, attrName)) {
810
+ attrValue = trimWhitespace(attrValue);
811
+ return options.minifyCSS(attrValue, 'media');
812
+ }
813
+ return attrValue;
814
+ }
815
+
816
+ function isMetaViewport(tag, attrs) {
817
+ if (tag !== 'meta') {
818
+ return false;
819
+ }
820
+ for (let i = 0, len = attrs.length; i < len; i++) {
821
+ if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
822
+ return true;
823
+ }
824
+ }
825
+ }
826
+
827
+ function isContentSecurityPolicy(tag, attrs) {
828
+ if (tag !== 'meta') {
829
+ return false;
830
+ }
831
+ for (let i = 0, len = attrs.length; i < len; i++) {
832
+ if (attrs[i].name.toLowerCase() === 'http-equiv' && attrs[i].value.toLowerCase() === 'content-security-policy') {
833
+ return true;
834
+ }
835
+ }
836
+ }
837
+
838
+ function ignoreCSS(id) {
839
+ return '/* clean-css ignore:start */' + id + '/* clean-css ignore:end */';
840
+ }
841
+
842
+ // Wrap CSS declarations for CleanCSS > 3.x
843
+ // See https://github.com/jakubpawlowicz/clean-css/issues/418
844
+ function wrapCSS(text, type) {
845
+ switch (type) {
846
+ case 'inline':
847
+ return '*{' + text + '}';
848
+ case 'media':
849
+ return '@media ' + text + '{a{top:0}}';
850
+ default:
851
+ return text;
852
+ }
853
+ }
854
+
855
+ function unwrapCSS(text, type) {
856
+ let matches;
857
+ switch (type) {
858
+ case 'inline':
859
+ matches = text.match(/^\*\{([\s\S]*)\}$/);
860
+ break;
861
+ case 'media':
862
+ matches = text.match(/^@media ([\s\S]*?)\s*{[\s\S]*}$/);
863
+ break;
864
+ }
865
+ return matches ? matches[1] : text;
866
+ }
867
+
868
+ async function cleanConditionalComment(comment, options) {
869
+ return options.processConditionalComments
870
+ ? await replaceAsync(comment, /^(\[if\s[^\]]+]>)([\s\S]*?)(<!\[endif])$/, async function (match, prefix, text, suffix) {
871
+ return prefix + await minifyHTML(text, options, true) + suffix;
872
+ })
873
+ : comment;
874
+ }
875
+
876
+ async function processScript(text, options, currentAttrs) {
877
+ for (let i = 0, len = currentAttrs.length; i < len; i++) {
878
+ if (currentAttrs[i].name.toLowerCase() === 'type' &&
879
+ options.processScripts.indexOf(currentAttrs[i].value) > -1) {
880
+ return await minifyHTML(text, options);
881
+ }
882
+ }
883
+ return text;
884
+ }
885
+
886
+ // Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
887
+ // with the following deviations:
888
+ // - retain <body> if followed by <noscript>
889
+ // - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow https://www.w3.org/TR/html5/syntax.html#optional-tags
890
+ // - retain all tags which are adjacent to non-standard HTML tags
891
+ const optionalStartTags = new Set(['html', 'head', 'body', 'colgroup', 'tbody']);
892
+ const optionalEndTags = new Set(['html', 'head', 'body', 'li', 'dt', 'dd', 'p', 'rb', 'rt', 'rtc', 'rp', 'optgroup', 'option', 'colgroup', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th']);
893
+ const headerTags = new Set(['meta', 'link', 'script', 'style', 'template', 'noscript']);
894
+ const descriptionTags = new Set(['dt', 'dd']);
895
+ const pBlockTags = new Set(['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']);
896
+ const pInlineTags = new Set(['a', 'audio', 'del', 'ins', 'map', 'noscript', 'video']);
897
+ const rubyTags = new Set(['rb', 'rt', 'rtc', 'rp']);
898
+ const rtcTag = new Set(['rb', 'rtc', 'rp']);
899
+ const optionTag = new Set(['option', 'optgroup']);
900
+ const tableContentTags = new Set(['tbody', 'tfoot']);
901
+ const tableSectionTags = new Set(['thead', 'tbody', 'tfoot']);
902
+ const cellTags = new Set(['td', 'th']);
903
+ const topLevelTags = new Set(['html', 'head', 'body']);
904
+ const compactTags = new Set(['html', 'body']);
905
+ const looseTags = new Set(['head', 'colgroup', 'caption']);
906
+ const trailingTags = new Set(['dt', 'thead']);
907
+ const htmlTags = new Set(['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', 'rb', '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']);
908
+
909
+ function canRemoveParentTag(optionalStartTag, tag) {
910
+ switch (optionalStartTag) {
911
+ case 'html':
912
+ case 'head':
913
+ return true;
914
+ case 'body':
915
+ return !headerTags.has(tag);
916
+ case 'colgroup':
917
+ return tag === 'col';
918
+ case 'tbody':
919
+ return tag === 'tr';
920
+ }
921
+ return false;
922
+ }
923
+
924
+ function isStartTagMandatory(optionalEndTag, tag) {
925
+ switch (tag) {
926
+ case 'colgroup':
927
+ return optionalEndTag === 'colgroup';
928
+ case 'tbody':
929
+ return tableSectionTags.has(optionalEndTag);
930
+ }
931
+ return false;
932
+ }
933
+
934
+ function canRemovePrecedingTag(optionalEndTag, tag) {
935
+ switch (optionalEndTag) {
936
+ case 'html':
937
+ case 'head':
938
+ case 'body':
939
+ case 'colgroup':
940
+ case 'caption':
941
+ return true;
942
+ case 'li':
943
+ case 'optgroup':
944
+ case 'tr':
945
+ return tag === optionalEndTag;
946
+ case 'dt':
947
+ case 'dd':
948
+ return descriptionTags.has(tag);
949
+ case 'p':
950
+ return pBlockTags.has(tag);
951
+ case 'rb':
952
+ case 'rt':
953
+ case 'rp':
954
+ return rubyTags.has(tag);
955
+ case 'rtc':
956
+ return rtcTag.has(tag);
957
+ case 'option':
958
+ return optionTag.has(tag);
959
+ case 'thead':
960
+ case 'tbody':
961
+ return tableContentTags.has(tag);
962
+ case 'tfoot':
963
+ return tag === 'tbody';
964
+ case 'td':
965
+ case 'th':
966
+ return cellTags.has(tag);
967
+ }
968
+ return false;
969
+ }
970
+
971
+ const reEmptyAttribute = new RegExp(
972
+ '^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
973
+ '?:down|up|over|move|out)|key(?:press|down|up)))$');
974
+
975
+ function canDeleteEmptyAttribute(tag, attrName, attrValue, options) {
976
+ const isValueEmpty = !attrValue || /^\s*$/.test(attrValue);
977
+ if (!isValueEmpty) {
978
+ return false;
979
+ }
980
+ if (typeof options.removeEmptyAttributes === 'function') {
981
+ return options.removeEmptyAttributes(attrName, tag);
982
+ }
983
+ return (tag === 'input' && attrName === 'value') || reEmptyAttribute.test(attrName);
984
+ }
985
+
986
+ function hasAttrName(name, attrs) {
987
+ for (let i = attrs.length - 1; i >= 0; i--) {
988
+ if (attrs[i].name === name) {
989
+ return true;
990
+ }
991
+ }
992
+ return false;
993
+ }
994
+
995
+ function canRemoveElement(tag, attrs) {
996
+ switch (tag) {
997
+ case 'textarea':
998
+ return false;
999
+ case 'audio':
1000
+ case 'script':
1001
+ case 'video':
1002
+ if (hasAttrName('src', attrs)) {
1003
+ return false;
1004
+ }
1005
+ break;
1006
+ case 'iframe':
1007
+ if (hasAttrName('src', attrs) || hasAttrName('srcdoc', attrs)) {
1008
+ return false;
1009
+ }
1010
+ break;
1011
+ case 'object':
1012
+ if (hasAttrName('data', attrs)) {
1013
+ return false;
1014
+ }
1015
+ break;
1016
+ case 'applet':
1017
+ if (hasAttrName('code', attrs)) {
1018
+ return false;
1019
+ }
1020
+ break;
1021
+ }
1022
+ return true;
1023
+ }
1024
+
1025
+ function canCollapseWhitespace(tag) {
1026
+ return !/^(?:script|style|pre|textarea)$/.test(tag);
1027
+ }
1028
+
1029
+ function canTrimWhitespace(tag) {
1030
+ return !/^(?:pre|textarea)$/.test(tag);
1031
+ }
1032
+
1033
+ async function normalizeAttr(attr, attrs, tag, options) {
1034
+ const attrName = options.name(attr.name);
1035
+ let attrValue = attr.value;
1036
+
1037
+ if (options.decodeEntities && attrValue) {
1038
+ attrValue = entities.decodeHTMLStrict(attrValue);
1039
+ }
1040
+
1041
+ if ((options.removeRedundantAttributes &&
1042
+ isAttributeRedundant(tag, attrName, attrValue, attrs)) ||
1043
+ (options.removeScriptTypeAttributes && tag === 'script' &&
1044
+ attrName === 'type' && isScriptTypeAttribute(attrValue) && !keepScriptTypeAttribute(attrValue)) ||
1045
+ (options.removeStyleLinkTypeAttributes && (tag === 'style' || tag === 'link') &&
1046
+ attrName === 'type' && isStyleLinkTypeAttribute(attrValue))) {
1047
+ return;
1048
+ }
1049
+
1050
+ if (attrValue) {
1051
+ attrValue = await cleanAttributeValue(tag, attrName, attrValue, options, attrs);
1052
+ }
1053
+
1054
+ if (options.removeEmptyAttributes &&
1055
+ canDeleteEmptyAttribute(tag, attrName, attrValue, options)) {
1056
+ return;
1057
+ }
1058
+
1059
+ if (options.decodeEntities && attrValue) {
1060
+ attrValue = attrValue.replace(/&(#?[0-9a-zA-Z]+;)/g, '&amp;$1');
1061
+ }
1062
+
1063
+ return {
1064
+ attr,
1065
+ name: attrName,
1066
+ value: attrValue
1067
+ };
1068
+ }
1069
+
1070
+ function buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr) {
1071
+ const attrName = normalized.name;
1072
+ let attrValue = normalized.value;
1073
+ const attr = normalized.attr;
1074
+ let attrQuote = attr.quote;
1075
+ let attrFragment;
1076
+ let emittedAttrValue;
1077
+
1078
+ if (typeof attrValue !== 'undefined' && (!options.removeAttributeQuotes ||
1079
+ ~attrValue.indexOf(uidAttr) || !canRemoveAttributeQuotes(attrValue))) {
1080
+ if (!options.preventAttributesEscaping) {
1081
+ if (typeof options.quoteCharacter === 'undefined') {
1082
+ const apos = (attrValue.match(/'/g) || []).length;
1083
+ const quot = (attrValue.match(/"/g) || []).length;
1084
+ attrQuote = apos < quot ? '\'' : '"';
1085
+ } else {
1086
+ attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
1087
+ }
1088
+ if (attrQuote === '"') {
1089
+ attrValue = attrValue.replace(/"/g, '&#34;');
1090
+ } else {
1091
+ attrValue = attrValue.replace(/'/g, '&#39;');
1092
+ }
1093
+ }
1094
+ emittedAttrValue = attrQuote + attrValue + attrQuote;
1095
+ if (!isLast && !options.removeTagWhitespace) {
1096
+ emittedAttrValue += ' ';
1097
+ }
1098
+ } else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) {
1099
+ // make sure trailing slash is not interpreted as HTML self-closing tag
1100
+ emittedAttrValue = attrValue;
1101
+ } else {
1102
+ emittedAttrValue = attrValue + ' ';
1103
+ }
1104
+
1105
+ if (typeof attrValue === 'undefined' || (options.collapseBooleanAttributes &&
1106
+ isBooleanAttribute(attrName.toLowerCase(), attrValue.toLowerCase()))) {
1107
+ attrFragment = attrName;
1108
+ if (!isLast) {
1109
+ attrFragment += ' ';
1110
+ }
1111
+ } else {
1112
+ attrFragment = attrName + attr.customAssign + emittedAttrValue;
1113
+ }
1114
+
1115
+ return attr.customOpen + attrFragment + attr.customClose;
1116
+ }
1117
+
1118
+ function identity(value) {
1119
+ return value;
1120
+ }
1121
+
1122
+ function identityAsync(value) {
1123
+ return Promise.resolve(value);
1124
+ }
1125
+
1126
+ const processOptions = (inputOptions) => {
1127
+ const options = {
1128
+ name: function (name) {
1129
+ return name.toLowerCase();
1130
+ },
1131
+ canCollapseWhitespace,
1132
+ canTrimWhitespace,
1133
+ html5: true,
1134
+ ignoreCustomComments: [
1135
+ /^!/,
1136
+ /^\s*#/
1137
+ ],
1138
+ ignoreCustomFragments: [
1139
+ /<%[\s\S]*?%>/,
1140
+ /<\?[\s\S]*?\?>/
1141
+ ],
1142
+ includeAutoGeneratedTags: true,
1143
+ log: identity,
1144
+ minifyCSS: identityAsync,
1145
+ minifyJS: identity,
1146
+ minifyURLs: identity
1147
+ };
1148
+
1149
+ Object.keys(inputOptions).forEach(function (key) {
1150
+ const option = inputOptions[key];
1151
+
1152
+ if (key === 'caseSensitive') {
1153
+ if (option) {
1154
+ options.name = identity;
1155
+ }
1156
+ } else if (key === 'log') {
1157
+ if (typeof option === 'function') {
1158
+ options.log = option;
1159
+ }
1160
+ } else if (key === 'minifyCSS' && typeof option !== 'function') {
1161
+ if (!option) {
1162
+ return;
1163
+ }
1164
+
1165
+ const cleanCssOptions = typeof option === 'object' ? option : {};
1166
+
1167
+ options.minifyCSS = async function (text, type) {
1168
+ text = text.replace(/(url\s*\(\s*)("|'|)(.*?)\2(\s*\))/ig, function (match, prefix, quote, url, suffix) {
1169
+ return prefix + quote + options.minifyURLs(url) + quote + suffix;
1170
+ });
1171
+
1172
+ const inputCSS = wrapCSS(text, type);
1173
+
1174
+ return new Promise((resolve) => {
1175
+ new CleanCSS(cleanCssOptions).minify(inputCSS, (_err, output) => {
1176
+ if (output.errors.length > 0) {
1177
+ output.errors.forEach(options.log);
1178
+ resolve(text);
1179
+ }
1180
+
1181
+ const outputCSS = unwrapCSS(output.styles, type);
1182
+ resolve(outputCSS);
1183
+ });
1184
+ });
1185
+ };
1186
+ } else if (key === 'minifyJS' && typeof option !== 'function') {
1187
+ if (!option) {
1188
+ return;
1189
+ }
1190
+
1191
+ const terserOptions = typeof option === 'object' ? option : {};
1192
+
1193
+ terserOptions.parse = {
1194
+ ...terserOptions.parse,
1195
+ bare_returns: false
1196
+ };
1197
+
1198
+ options.minifyJS = async function (text, inline) {
1199
+ const start = text.match(/^\s*<!--.*/);
1200
+ const code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
1201
+
1202
+ terserOptions.parse.bare_returns = inline;
1203
+
1204
+ try {
1205
+ const result = await terser.minify(code, terserOptions);
1206
+ return result.code.replace(/;$/, '');
1207
+ } catch (error) {
1208
+ options.log(error);
1209
+ return text;
1210
+ }
1211
+ };
1212
+ } else if (key === 'minifyURLs' && typeof option !== 'function') {
1213
+ if (!option) {
1214
+ return;
1215
+ }
1216
+
1217
+ let relateUrlOptions = option;
1218
+
1219
+ if (typeof option === 'string') {
1220
+ relateUrlOptions = { site: option };
1221
+ } else if (typeof option !== 'object') {
1222
+ relateUrlOptions = {};
1223
+ }
1224
+
1225
+ options.minifyURLs = function (text) {
1226
+ try {
1227
+ return RelateURL.relate(text, relateUrlOptions);
1228
+ } catch (err) {
1229
+ options.log(err);
1230
+ return text;
1231
+ }
1232
+ };
1233
+ } else {
1234
+ options[key] = option;
1235
+ }
1236
+ });
1237
+ return options;
1238
+ };
1239
+
1240
+ function uniqueId(value) {
1241
+ let id;
1242
+ do {
1243
+ id = Math.random().toString(36).replace(/^0\.[0-9]*/, '');
1244
+ } while (~value.indexOf(id));
1245
+ return id;
1246
+ }
1247
+
1248
+ const specialContentTags = new Set(['script', 'style']);
1249
+
1250
+ async function createSortFns(value, options, uidIgnore, uidAttr) {
1251
+ const attrChains = options.sortAttributes && Object.create(null);
1252
+ const classChain = options.sortClassName && new TokenChain();
1253
+
1254
+ function attrNames(attrs) {
1255
+ return attrs.map(function (attr) {
1256
+ return options.name(attr.name);
1257
+ });
1258
+ }
1259
+
1260
+ function shouldSkipUID(token, uid) {
1261
+ return !uid || token.indexOf(uid) === -1;
1262
+ }
1263
+
1264
+ function shouldSkipUIDs(token) {
1265
+ return shouldSkipUID(token, uidIgnore) && shouldSkipUID(token, uidAttr);
1266
+ }
1267
+
1268
+ async function scan(input) {
1269
+ let currentTag, currentType;
1270
+ const parser = new HTMLParser(input, {
1271
+ start: function (tag, attrs) {
1272
+ if (attrChains) {
1273
+ if (!attrChains[tag]) {
1274
+ attrChains[tag] = new TokenChain();
1275
+ }
1276
+ attrChains[tag].add(attrNames(attrs).filter(shouldSkipUIDs));
1277
+ }
1278
+ for (let i = 0, len = attrs.length; i < len; i++) {
1279
+ const attr = attrs[i];
1280
+ if (classChain && attr.value && options.name(attr.name) === 'class') {
1281
+ classChain.add(trimWhitespace(attr.value).split(/[ \t\n\f\r]+/).filter(shouldSkipUIDs));
1282
+ } else if (options.processScripts && attr.name.toLowerCase() === 'type') {
1283
+ currentTag = tag;
1284
+ currentType = attr.value;
1285
+ }
1286
+ }
1287
+ },
1288
+ end: function () {
1289
+ currentTag = '';
1290
+ },
1291
+ chars: async function (text) {
1292
+ if (options.processScripts && specialContentTags.has(currentTag) &&
1293
+ options.processScripts.indexOf(currentType) > -1) {
1294
+ await scan(text);
1295
+ }
1296
+ }
1297
+ });
1298
+
1299
+ await parser.parse();
1300
+ }
1301
+
1302
+ const log = options.log;
1303
+ options.log = identity;
1304
+ options.sortAttributes = false;
1305
+ options.sortClassName = false;
1306
+ await scan(await minifyHTML(value, options));
1307
+ options.log = log;
1308
+ if (attrChains) {
1309
+ const attrSorters = Object.create(null);
1310
+ for (const tag in attrChains) {
1311
+ attrSorters[tag] = attrChains[tag].createSorter();
1312
+ }
1313
+ options.sortAttributes = function (tag, attrs) {
1314
+ const sorter = attrSorters[tag];
1315
+ if (sorter) {
1316
+ const attrMap = Object.create(null);
1317
+ const names = attrNames(attrs);
1318
+ names.forEach(function (name, index) {
1319
+ (attrMap[name] || (attrMap[name] = [])).push(attrs[index]);
1320
+ });
1321
+ sorter.sort(names).forEach(function (name, index) {
1322
+ attrs[index] = attrMap[name].shift();
1323
+ });
1324
+ }
1325
+ };
1326
+ }
1327
+ if (classChain) {
1328
+ const sorter = classChain.createSorter();
1329
+ options.sortClassName = function (value) {
1330
+ return sorter.sort(value.split(/[ \n\f\r]+/)).join(' ');
1331
+ };
1332
+ }
1333
+ }
1334
+
1335
+ async function minifyHTML(value, options, partialMarkup) {
1336
+ if (options.collapseWhitespace) {
1337
+ value = collapseWhitespace(value, options, true, true);
1338
+ }
1339
+
1340
+ const buffer = [];
1341
+ let charsPrevTag;
1342
+ let currentChars = '';
1343
+ let hasChars;
1344
+ let currentTag = '';
1345
+ let currentAttrs = [];
1346
+ const stackNoTrimWhitespace = [];
1347
+ const stackNoCollapseWhitespace = [];
1348
+ let optionalStartTag = '';
1349
+ let optionalEndTag = '';
1350
+ const ignoredMarkupChunks = [];
1351
+ const ignoredCustomMarkupChunks = [];
1352
+ let uidIgnore;
1353
+ let uidAttr;
1354
+ let uidPattern;
1355
+
1356
+ // temporarily replace ignored chunks with comments,
1357
+ // so that we don't have to worry what's there.
1358
+ // for all we care there might be
1359
+ // completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
1360
+ value = value.replace(/<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g, function (match, group1) {
1361
+ if (!uidIgnore) {
1362
+ uidIgnore = uniqueId(value);
1363
+ const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
1364
+ if (options.ignoreCustomComments) {
1365
+ options.ignoreCustomComments = options.ignoreCustomComments.slice();
1366
+ } else {
1367
+ options.ignoreCustomComments = [];
1368
+ }
1369
+ options.ignoreCustomComments.push(pattern);
1370
+ }
1371
+ const token = '<!--' + uidIgnore + ignoredMarkupChunks.length + '-->';
1372
+ ignoredMarkupChunks.push(group1);
1373
+ return token;
1374
+ });
1375
+
1376
+ const customFragments = options.ignoreCustomFragments.map(function (re) {
1377
+ return re.source;
1378
+ });
1379
+ if (customFragments.length) {
1380
+ const reCustomIgnore = new RegExp('\\s*(?:' + customFragments.join('|') + ')+\\s*', 'g');
1381
+ // temporarily replace custom ignored fragments with unique attributes
1382
+ value = value.replace(reCustomIgnore, function (match) {
1383
+ if (!uidAttr) {
1384
+ uidAttr = uniqueId(value);
1385
+ uidPattern = new RegExp('(\\s*)' + uidAttr + '([0-9]+)' + uidAttr + '(\\s*)', 'g');
1386
+
1387
+ if (options.minifyCSS) {
1388
+ options.minifyCSS = (function (fn) {
1389
+ return function (text, type) {
1390
+ text = text.replace(uidPattern, function (match, prefix, index) {
1391
+ const chunks = ignoredCustomMarkupChunks[+index];
1392
+ return chunks[1] + uidAttr + index + uidAttr + chunks[2];
1393
+ });
1394
+
1395
+ const ids = [];
1396
+ new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) {
1397
+ const match = uidPattern.exec(warning);
1398
+ if (match) {
1399
+ const id = uidAttr + match[2] + uidAttr;
1400
+ text = text.replace(id, ignoreCSS(id));
1401
+ ids.push(id);
1402
+ }
1403
+ });
1404
+
1405
+ return fn(text, type).then(chunk => {
1406
+ ids.forEach(function (id) {
1407
+ chunk = chunk.replace(ignoreCSS(id), id);
1408
+ });
1409
+
1410
+ return chunk;
1411
+ });
1412
+ };
1413
+ })(options.minifyCSS);
1414
+ }
1415
+
1416
+ if (options.minifyJS) {
1417
+ options.minifyJS = (function (fn) {
1418
+ return function (text, type) {
1419
+ return fn(text.replace(uidPattern, function (match, prefix, index) {
1420
+ const chunks = ignoredCustomMarkupChunks[+index];
1421
+ return chunks[1] + uidAttr + index + uidAttr + chunks[2];
1422
+ }), type);
1423
+ };
1424
+ })(options.minifyJS);
1425
+ }
1426
+ }
1427
+
1428
+ const token = uidAttr + ignoredCustomMarkupChunks.length + uidAttr;
1429
+ ignoredCustomMarkupChunks.push(/^(\s*)[\s\S]*?(\s*)$/.exec(match));
1430
+ return '\t' + token + '\t';
1431
+ });
1432
+ }
1433
+
1434
+ if ((options.sortAttributes && typeof options.sortAttributes !== 'function') ||
1435
+ (options.sortClassName && typeof options.sortClassName !== 'function')) {
1436
+ await createSortFns(value, options, uidIgnore, uidAttr);
1437
+ }
1438
+
1439
+ function _canCollapseWhitespace(tag, attrs) {
1440
+ return options.canCollapseWhitespace(tag, attrs, canCollapseWhitespace);
1441
+ }
1442
+
1443
+ function _canTrimWhitespace(tag, attrs) {
1444
+ return options.canTrimWhitespace(tag, attrs, canTrimWhitespace);
1445
+ }
1446
+
1447
+ function removeStartTag() {
1448
+ let index = buffer.length - 1;
1449
+ while (index > 0 && !/^<[^/!]/.test(buffer[index])) {
1450
+ index--;
1451
+ }
1452
+ buffer.length = Math.max(0, index);
1453
+ }
1454
+
1455
+ function removeEndTag() {
1456
+ let index = buffer.length - 1;
1457
+ while (index > 0 && !/^<\//.test(buffer[index])) {
1458
+ index--;
1459
+ }
1460
+ buffer.length = Math.max(0, index);
1461
+ }
1462
+
1463
+ // look for trailing whitespaces, bypass any inline tags
1464
+ function trimTrailingWhitespace(index, nextTag) {
1465
+ for (let endTag = null; index >= 0 && _canTrimWhitespace(endTag); index--) {
1466
+ const str = buffer[index];
1467
+ const match = str.match(/^<\/([\w:-]+)>$/);
1468
+ if (match) {
1469
+ endTag = match[1];
1470
+ } else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
1471
+ break;
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ // look for trailing whitespaces from previously processed text
1477
+ // which may not be trimmed due to a following comment or an empty
1478
+ // element which has now been removed
1479
+ function squashTrailingWhitespace(nextTag) {
1480
+ let charsIndex = buffer.length - 1;
1481
+ if (buffer.length > 1) {
1482
+ const item = buffer[buffer.length - 1];
1483
+ if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
1484
+ charsIndex--;
1485
+ }
1486
+ }
1487
+ trimTrailingWhitespace(charsIndex, nextTag);
1488
+ }
1489
+
1490
+ const parser = new HTMLParser(value, {
1491
+ partialMarkup,
1492
+ continueOnParseError: options.continueOnParseError,
1493
+ customAttrAssign: options.customAttrAssign,
1494
+ customAttrSurround: options.customAttrSurround,
1495
+ html5: options.html5,
1496
+
1497
+ start: async function (tag, attrs, unary, unarySlash, autoGenerated) {
1498
+ if (tag.toLowerCase() === 'svg') {
1499
+ options = Object.create(options);
1500
+ options.caseSensitive = true;
1501
+ options.keepClosingSlash = true;
1502
+ options.name = identity;
1503
+ }
1504
+ tag = options.name(tag);
1505
+ currentTag = tag;
1506
+ charsPrevTag = tag;
1507
+ if (!inlineTextTags.has(tag)) {
1508
+ currentChars = '';
1509
+ }
1510
+ hasChars = false;
1511
+ currentAttrs = attrs;
1512
+
1513
+ let optional = options.removeOptionalTags;
1514
+ if (optional) {
1515
+ const htmlTag = htmlTags.has(tag);
1516
+ // <html> may be omitted if first thing inside is not comment
1517
+ // <head> may be omitted if first thing inside is an element
1518
+ // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
1519
+ // <colgroup> may be omitted if first thing inside is <col>
1520
+ // <tbody> may be omitted if first thing inside is <tr>
1521
+ if (htmlTag && canRemoveParentTag(optionalStartTag, tag)) {
1522
+ removeStartTag();
1523
+ }
1524
+ optionalStartTag = '';
1525
+ // end-tag-followed-by-start-tag omission rules
1526
+ if (htmlTag && canRemovePrecedingTag(optionalEndTag, tag)) {
1527
+ removeEndTag();
1528
+ // <colgroup> cannot be omitted if preceding </colgroup> is omitted
1529
+ // <tbody> cannot be omitted if preceding </tbody>, </thead> or </tfoot> is omitted
1530
+ optional = !isStartTagMandatory(optionalEndTag, tag);
1531
+ }
1532
+ optionalEndTag = '';
1533
+ }
1534
+
1535
+ // set whitespace flags for nested tags (eg. <code> within a <pre>)
1536
+ if (options.collapseWhitespace) {
1537
+ if (!stackNoTrimWhitespace.length) {
1538
+ squashTrailingWhitespace(tag);
1539
+ }
1540
+ if (!unary) {
1541
+ if (!_canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
1542
+ stackNoTrimWhitespace.push(tag);
1543
+ }
1544
+ if (!_canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length) {
1545
+ stackNoCollapseWhitespace.push(tag);
1546
+ }
1547
+ }
1548
+ }
1549
+
1550
+ const openTag = '<' + tag;
1551
+ const hasUnarySlash = unarySlash && options.keepClosingSlash;
1552
+
1553
+ buffer.push(openTag);
1554
+
1555
+ if (options.sortAttributes) {
1556
+ options.sortAttributes(tag, attrs);
1557
+ }
1558
+
1559
+ const parts = [];
1560
+ for (let i = attrs.length, isLast = true; --i >= 0;) {
1561
+ const normalized = await normalizeAttr(attrs[i], attrs, tag, options);
1562
+ if (normalized) {
1563
+ parts.unshift(buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr));
1564
+ isLast = false;
1565
+ }
1566
+ }
1567
+ if (parts.length > 0) {
1568
+ buffer.push(' ');
1569
+ buffer.push.apply(buffer, parts);
1570
+ } else if (optional && optionalStartTags.has(tag)) {
1571
+ // start tag must never be omitted if it has any attributes
1572
+ optionalStartTag = tag;
1573
+ }
1574
+
1575
+ buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
1576
+
1577
+ if (autoGenerated && !options.includeAutoGeneratedTags) {
1578
+ removeStartTag();
1579
+ optionalStartTag = '';
1580
+ }
1581
+ },
1582
+ end: function (tag, attrs, autoGenerated) {
1583
+ if (tag.toLowerCase() === 'svg') {
1584
+ options = Object.getPrototypeOf(options);
1585
+ }
1586
+ tag = options.name(tag);
1587
+
1588
+ // check if current tag is in a whitespace stack
1589
+ if (options.collapseWhitespace) {
1590
+ if (stackNoTrimWhitespace.length) {
1591
+ if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
1592
+ stackNoTrimWhitespace.pop();
1593
+ }
1594
+ } else {
1595
+ squashTrailingWhitespace('/' + tag);
1596
+ }
1597
+ if (stackNoCollapseWhitespace.length &&
1598
+ tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
1599
+ stackNoCollapseWhitespace.pop();
1600
+ }
1601
+ }
1602
+
1603
+ let isElementEmpty = false;
1604
+ if (tag === currentTag) {
1605
+ currentTag = '';
1606
+ isElementEmpty = !hasChars;
1607
+ }
1608
+
1609
+ if (options.removeOptionalTags) {
1610
+ // <html>, <head> or <body> may be omitted if the element is empty
1611
+ if (isElementEmpty && topLevelTags.has(optionalStartTag)) {
1612
+ removeStartTag();
1613
+ }
1614
+ optionalStartTag = '';
1615
+ // </html> or </body> may be omitted if not followed by comment
1616
+ // </head> may be omitted if not followed by space or comment
1617
+ // </p> may be omitted if no more content in non-</a> parent
1618
+ // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1619
+ if (htmlTags.has(tag) && optionalEndTag && !trailingTags.has(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags.has(tag))) {
1620
+ removeEndTag();
1621
+ }
1622
+ optionalEndTag = optionalEndTags.has(tag) ? tag : '';
1623
+ }
1624
+
1625
+ if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
1626
+ // remove last "element" from buffer
1627
+ removeStartTag();
1628
+ optionalStartTag = '';
1629
+ optionalEndTag = '';
1630
+ } else {
1631
+ if (autoGenerated && !options.includeAutoGeneratedTags) {
1632
+ optionalEndTag = '';
1633
+ } else {
1634
+ buffer.push('</' + tag + '>');
1635
+ }
1636
+ charsPrevTag = '/' + tag;
1637
+ if (!inlineTags.has(tag)) {
1638
+ currentChars = '';
1639
+ } else if (isElementEmpty) {
1640
+ currentChars += '|';
1641
+ }
1642
+ }
1643
+ },
1644
+ chars: async function (text, prevTag, nextTag) {
1645
+ prevTag = prevTag === '' ? 'comment' : prevTag;
1646
+ nextTag = nextTag === '' ? 'comment' : nextTag;
1647
+ if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
1648
+ text = entities.decodeHTML(text);
1649
+ }
1650
+ if (options.collapseWhitespace) {
1651
+ if (!stackNoTrimWhitespace.length) {
1652
+ if (prevTag === 'comment') {
1653
+ const prevComment = buffer[buffer.length - 1];
1654
+ if (prevComment.indexOf(uidIgnore) === -1) {
1655
+ if (!prevComment) {
1656
+ prevTag = charsPrevTag;
1657
+ }
1658
+ if (buffer.length > 1 && (!prevComment || (!options.conservativeCollapse && / $/.test(currentChars)))) {
1659
+ const charsIndex = buffer.length - 2;
1660
+ buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function (trailingSpaces) {
1661
+ text = trailingSpaces + text;
1662
+ return '';
1663
+ });
1664
+ }
1665
+ }
1666
+ }
1667
+ if (prevTag) {
1668
+ if (prevTag === '/nobr' || prevTag === 'wbr') {
1669
+ if (/^\s/.test(text)) {
1670
+ let tagIndex = buffer.length - 1;
1671
+ while (tagIndex > 0 && buffer[tagIndex].lastIndexOf('<' + prevTag) !== 0) {
1672
+ tagIndex--;
1673
+ }
1674
+ trimTrailingWhitespace(tagIndex - 1, 'br');
1675
+ }
1676
+ } else if (inlineTextTags.has(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
1677
+ text = collapseWhitespace(text, options, /(?:^|\s)$/.test(currentChars));
1678
+ }
1679
+ }
1680
+ if (prevTag || nextTag) {
1681
+ text = collapseWhitespaceSmart(text, prevTag, nextTag, options);
1682
+ } else {
1683
+ text = collapseWhitespace(text, options, true, true);
1684
+ }
1685
+ if (!text && /\s$/.test(currentChars) && prevTag && prevTag.charAt(0) === '/') {
1686
+ trimTrailingWhitespace(buffer.length - 1, nextTag);
1687
+ }
1688
+ }
1689
+ if (!stackNoCollapseWhitespace.length && nextTag !== 'html' && !(prevTag && nextTag)) {
1690
+ text = collapseWhitespace(text, options, false, false, true);
1691
+ }
1692
+ }
1693
+ if (options.processScripts && specialContentTags.has(currentTag)) {
1694
+ text = await processScript(text, options, currentAttrs);
1695
+ }
1696
+ if (isExecutableScript(currentTag, currentAttrs)) {
1697
+ text = await options.minifyJS(text);
1698
+ }
1699
+ if (isStyleSheet(currentTag, currentAttrs)) {
1700
+ text = await options.minifyCSS(text);
1701
+ }
1702
+ if (options.removeOptionalTags && text) {
1703
+ // <html> may be omitted if first thing inside is not comment
1704
+ // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
1705
+ if (optionalStartTag === 'html' || (optionalStartTag === 'body' && !/^\s/.test(text))) {
1706
+ removeStartTag();
1707
+ }
1708
+ optionalStartTag = '';
1709
+ // </html> or </body> may be omitted if not followed by comment
1710
+ // </head>, </colgroup> or </caption> may be omitted if not followed by space or comment
1711
+ if (compactTags.has(optionalEndTag) || (looseTags.has(optionalEndTag) && !/^\s/.test(text))) {
1712
+ removeEndTag();
1713
+ }
1714
+ optionalEndTag = '';
1715
+ }
1716
+ charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
1717
+ if (options.decodeEntities && text && !specialContentTags.has(currentTag)) {
1718
+ // Escape any `&` symbols that start either:
1719
+ // 1) a legacy named character reference (i.e. one that doesn't end with `;`)
1720
+ // 2) or any other character reference (i.e. one that does end with `;`)
1721
+ // Note that `&` can be escaped as `&amp`, without the semi-colon.
1722
+ // https://mathiasbynens.be/notes/ambiguous-ampersands
1723
+ text = text.replace(/&((?:Iacute|aacute|uacute|plusmn|Otilde|otilde|agrave|Agrave|Yacute|yacute|Oslash|oslash|atilde|Atilde|brvbar|ccedil|Ccedil|Ograve|curren|divide|eacute|Eacute|ograve|Oacute|egrave|Egrave|Ugrave|frac12|frac14|frac34|ugrave|oacute|iacute|Ntilde|ntilde|Uacute|middot|igrave|Igrave|iquest|Aacute|cedil|laquo|micro|iexcl|Icirc|icirc|acirc|Ucirc|Ecirc|ocirc|Ocirc|ecirc|ucirc|Aring|aring|AElig|aelig|acute|pound|raquo|Acirc|times|THORN|szlig|thorn|COPY|auml|ordf|ordm|Uuml|macr|uuml|Auml|ouml|Ouml|para|nbsp|euml|quot|QUOT|Euml|yuml|cent|sect|copy|sup1|sup2|sup3|iuml|Iuml|ETH|shy|reg|not|yen|amp|AMP|REG|uml|eth|deg|gt|GT|LT|lt)(?!;)|(?:#?[0-9a-zA-Z]+;))/g, '&amp$1').replace(/</g, '&lt;');
1724
+ }
1725
+ if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
1726
+ text = text.replace(uidPattern, function (match, prefix, index) {
1727
+ return ignoredCustomMarkupChunks[+index][0];
1728
+ });
1729
+ }
1730
+ currentChars += text;
1731
+ if (text) {
1732
+ hasChars = true;
1733
+ }
1734
+ buffer.push(text);
1735
+ },
1736
+ comment: async function (text, nonStandard) {
1737
+ const prefix = nonStandard ? '<!' : '<!--';
1738
+ const suffix = nonStandard ? '>' : '-->';
1739
+ if (isConditionalComment(text)) {
1740
+ text = prefix + await cleanConditionalComment(text, options) + suffix;
1741
+ } else if (options.removeComments) {
1742
+ if (isIgnoredComment(text, options)) {
1743
+ text = '<!--' + text + '-->';
1744
+ } else {
1745
+ text = '';
1746
+ }
1747
+ } else {
1748
+ text = prefix + text + suffix;
1749
+ }
1750
+ if (options.removeOptionalTags && text) {
1751
+ // preceding comments suppress tag omissions
1752
+ optionalStartTag = '';
1753
+ optionalEndTag = '';
1754
+ }
1755
+ buffer.push(text);
1756
+ },
1757
+ doctype: function (doctype) {
1758
+ buffer.push(options.useShortDoctype
1759
+ ? '<!doctype' +
1760
+ (options.removeTagWhitespace ? '' : ' ') + 'html>'
1761
+ : collapseWhitespaceAll(doctype));
1762
+ }
1763
+ });
1764
+
1765
+ await parser.parse();
1766
+
1767
+ if (options.removeOptionalTags) {
1768
+ // <html> may be omitted if first thing inside is not comment
1769
+ // <head> or <body> may be omitted if empty
1770
+ if (topLevelTags.has(optionalStartTag)) {
1771
+ removeStartTag();
1772
+ }
1773
+ // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1774
+ if (optionalEndTag && !trailingTags.has(optionalEndTag)) {
1775
+ removeEndTag();
1776
+ }
1777
+ }
1778
+ if (options.collapseWhitespace) {
1779
+ squashTrailingWhitespace('br');
1780
+ }
1781
+
1782
+ return joinResultSegments(buffer, options, uidPattern
1783
+ ? function (str) {
1784
+ return str.replace(uidPattern, function (match, prefix, index, suffix) {
1785
+ let chunk = ignoredCustomMarkupChunks[+index][0];
1786
+ if (options.collapseWhitespace) {
1787
+ if (prefix !== '\t') {
1788
+ chunk = prefix + chunk;
1789
+ }
1790
+ if (suffix !== '\t') {
1791
+ chunk += suffix;
1792
+ }
1793
+ return collapseWhitespace(chunk, {
1794
+ preserveLineBreaks: options.preserveLineBreaks,
1795
+ conservativeCollapse: !options.trimCustomFragments
1796
+ }, /^[ \n\r\t\f]/.test(chunk), /[ \n\r\t\f]$/.test(chunk));
1797
+ }
1798
+ return chunk;
1799
+ });
1800
+ }
1801
+ : identity, uidIgnore
1802
+ ? function (str) {
1803
+ return str.replace(new RegExp('<!--' + uidIgnore + '([0-9]+)-->', 'g'), function (match, index) {
1804
+ return ignoredMarkupChunks[+index];
1805
+ });
1806
+ }
1807
+ : identity);
1808
+ }
1809
+
1810
+ function joinResultSegments(results, options, restoreCustom, restoreIgnore) {
1811
+ let str;
1812
+ const maxLineLength = options.maxLineLength;
1813
+ const noNewlinesBeforeTagClose = options.noNewlinesBeforeTagClose;
1814
+
1815
+ if (maxLineLength) {
1816
+ let line = ''; const lines = [];
1817
+ while (results.length) {
1818
+ const len = line.length;
1819
+ const end = results[0].indexOf('\n');
1820
+ const isClosingTag = Boolean(results[0].match(endTag));
1821
+ const shouldKeepSameLine = noNewlinesBeforeTagClose && isClosingTag;
1822
+
1823
+ if (end < 0) {
1824
+ line += restoreIgnore(restoreCustom(results.shift()));
1825
+ } else {
1826
+ line += restoreIgnore(restoreCustom(results[0].slice(0, end)));
1827
+ results[0] = results[0].slice(end + 1);
1828
+ }
1829
+ if (len > 0 && line.length > maxLineLength && !shouldKeepSameLine) {
1830
+ lines.push(line.slice(0, len));
1831
+ line = line.slice(len);
1832
+ } else if (end >= 0) {
1833
+ lines.push(line);
1834
+ line = '';
1835
+ }
1836
+ }
1837
+ if (line) {
1838
+ lines.push(line);
1839
+ }
1840
+ str = lines.join('\n');
1841
+ } else {
1842
+ str = restoreIgnore(restoreCustom(results.join('')));
1843
+ }
1844
+ return options.collapseWhitespace ? collapseWhitespace(str, options, true, true) : str;
1845
+ }
1846
+
1847
+ const minify = async function (value, options) {
1848
+ const start = Date.now();
1849
+ options = processOptions(options || {});
1850
+ const result = await minifyHTML(value, options);
1851
+ options.log('minified in: ' + (Date.now() - start) + 'ms');
1852
+ return result;
1853
+ };
1854
+
1855
+ var htmlminifier = { minify };
1856
+
1857
+ exports.default = htmlminifier;
1858
+ exports.minify = minify;