source: imaps-frontend/node_modules/html-minifier-terser/src/htmlminifier.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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