html-minifier 1.2.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1821 +0,0 @@
1
- /*!
2
- * HTMLMinifier v1.2.0 (http://kangax.github.io/html-minifier/)
3
- * Copyright 2010-2016 Juriy "kangax" Zaytsev
4
- * Licensed under the MIT license
5
- */
6
- /*!
7
- * HTML Parser By John Resig (ejohn.org)
8
- * Modified by Juriy "kangax" Zaytsev
9
- * Original code by Erik Arvidsson, Mozilla Public License
10
- * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
11
- */
12
-
13
- /*
14
- * // Use like so:
15
- * HTMLParser(htmlString, {
16
- * start: function(tag, attrs, unary) {},
17
- * end: function(tag) {},
18
- * chars: function(text) {},
19
- * comment: function(text) {}
20
- * });
21
- *
22
- * // or to get an XML string:
23
- * HTMLtoXML(htmlString);
24
- *
25
- * // or to get an XML DOM Document
26
- * HTMLtoDOM(htmlString);
27
- *
28
- * // or to inject into an existing document/DOM node
29
- * HTMLtoDOM(htmlString, document);
30
- * HTMLtoDOM(htmlString, document.body);
31
- *
32
- */
33
-
34
- /* global ActiveXObject, DOMDocument */
35
-
36
- (function(global) {
37
- 'use strict';
38
-
39
- // Regular Expressions for parsing tags and attributes
40
- var singleAttrIdentifier = /([\w:\.-]+)/,
41
- singleAttrAssign = /=/,
42
- singleAttrAssigns = [ singleAttrAssign ],
43
- singleAttrValues = [
44
- /"((?:\\.|[^"])*)"/.source, // attr value double quotes
45
- /'((?:\\.|[^'])*)'/.source, // attr value, single quotes
46
- /([^>\s]+)/.source // attr value, no quotes
47
- ],
48
- startTagOpen = /^<([\w:-]+)/,
49
- startTagClose = /\s*(\/?)>/,
50
- endTag = /^<\/([\w:-]+)[^>]*>/,
51
- endingSlash = /\/>$/,
52
- doctype = /^<!DOCTYPE [^>]+>/i;
53
-
54
- var IS_REGEX_CAPTURING_BROKEN = false;
55
- 'x'.replace(/x(.)?/g, function(m, g) {
56
- IS_REGEX_CAPTURING_BROKEN = g === '';
57
- });
58
-
59
- // Empty Elements
60
- var empty = makeMap('area,base,basefont,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr');
61
-
62
- // Inline Elements
63
- var inline = makeMap('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');
64
-
65
- // Elements that you can, intentionally, leave open
66
- // (and which close themselves)
67
- var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source');
68
-
69
- // Attributes that have their values filled in disabled='disabled'
70
- var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
71
-
72
- // Special Elements (can contain anything)
73
- var special = makeMap('script,style');
74
-
75
- var reCache = {}, stackedTag, reStackedTag, tagMatch;
76
-
77
- function startTagForHandler( handler ) {
78
- var customStartTagAttrs;
79
-
80
- var startTagAttrs = new RegExp(
81
- '(?:\\s*[\\w:\\.-]+'
82
- + '(?:\\s*'
83
- + '(?:' + joinSingleAttrAssigns(handler) + ')'
84
- + '\\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\\s]+)'
85
- + ')?'
86
- + ')*'
87
- );
88
-
89
- if ( handler.customAttrSurround ) {
90
- var attrClauses = [];
91
-
92
- for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
93
- // Capture the custom attribute opening and closing markup surrounding the standard attribute rules
94
- attrClauses[i] = '(?:\\s*'
95
- + handler.customAttrSurround[i][0].source
96
- + '\\s*'
97
- + startTagAttrs.source
98
- + '\\s*'
99
- + handler.customAttrSurround[i][1].source
100
- + ')';
101
- }
102
- attrClauses.unshift(startTagAttrs.source);
103
-
104
- customStartTagAttrs = new RegExp(
105
- '((?:' + attrClauses.join('|') + ')*)'
106
- );
107
- }
108
- else {
109
- // No custom attribute wrappers specified, so just capture the standard attribute rules
110
- customStartTagAttrs = new RegExp('(' + startTagAttrs.source + ')');
111
- }
112
-
113
- return new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source);
114
- }
115
-
116
- function attrForHandler( handler ) {
117
- var singleAttr = new RegExp(
118
- singleAttrIdentifier.source
119
- + '(?:\\s*'
120
- + '(' + joinSingleAttrAssigns( handler ) + ')'
121
- + '\\s*'
122
- + '(?:'
123
- + singleAttrValues.join('|')
124
- + ')'
125
- + ')?'
126
- );
127
-
128
- if ( handler.customAttrSurround ) {
129
- var attrClauses = [];
130
- for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
131
- attrClauses[i] = '(?:'
132
- + '(' + handler.customAttrSurround[i][0].source + ')\\s*'
133
- + singleAttr.source
134
- + '\\s*(' + handler.customAttrSurround[i][1].source + ')'
135
- + ')';
136
- }
137
- attrClauses.unshift('(?:' + singleAttr.source + ')');
138
-
139
- return new RegExp(attrClauses.join('|'), 'g');
140
- }
141
- else {
142
- return new RegExp(singleAttr.source, 'g');
143
- }
144
- }
145
-
146
- function joinSingleAttrAssigns( handler ) {
147
- return singleAttrAssigns.concat(
148
- handler.customAttrAssign || []
149
- ).map(function (assign) {
150
- return '(?:' + assign.source + ')';
151
- }).join('|');
152
- }
153
-
154
- var HTMLParser = global.HTMLParser = function( html, handler ) {
155
- var index, match, stack = [], last = html, prevTag, nextTag;
156
- stack.last = function() {
157
- var last = this[ this.length - 1 ];
158
- return last && last.tag;
159
- };
160
-
161
- var startTag = startTagForHandler(handler);
162
- var attr = attrForHandler(handler);
163
-
164
- while ( html ) {
165
- // Make sure we're not in a script or style element
166
- if ( !stack.last() || !special[ stack.last() ] ) {
167
-
168
- // Comment:
169
- if ( /^<!--/.test( html ) ) {
170
- index = html.indexOf('-->');
171
-
172
- if ( index >= 0 ) {
173
- if ( handler.comment ) {
174
- handler.comment( html.substring( 4, index ) );
175
- }
176
- html = html.substring( index + 3 );
177
- prevTag = '';
178
- continue;
179
- }
180
- }
181
-
182
- // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
183
- if ( /^<!\[/.test( html ) ) {
184
- index = html.indexOf(']>');
185
-
186
- if (index >= 0) {
187
- if ( handler.comment ) {
188
- handler.comment( html.substring(2, index + 1 ), true /* non-standard */ );
189
- }
190
- html = html.substring( index + 2 );
191
- prevTag = '';
192
- continue;
193
- }
194
- }
195
-
196
- // Doctype:
197
- if ( (match = doctype.exec( html )) ) {
198
- if ( handler.doctype ) {
199
- handler.doctype( match[0] );
200
- }
201
- html = html.substring( match[0].length );
202
- prevTag = '';
203
- continue;
204
- }
205
-
206
- // End tag:
207
- if ( /^<\//.test( html ) ) {
208
- match = html.match( endTag );
209
- if ( match ) {
210
- html = html.substring( match[0].length );
211
- match[0].replace( endTag, parseEndTag );
212
- prevTag = '/' + match[1].toLowerCase();
213
- continue;
214
- }
215
-
216
- }
217
- // Start tag:
218
- if ( /^</.test( html ) ) {
219
- match = html.match( startTag );
220
- if ( match ) {
221
- html = html.substring( match[0].length );
222
- match[0].replace( startTag, parseStartTag );
223
- prevTag = match[1].toLowerCase();
224
- continue;
225
- }
226
- }
227
-
228
- index = html.indexOf('<');
229
-
230
- var text = index < 0 ? html : html.substring( 0, index );
231
- html = index < 0 ? '' : html.substring( index );
232
-
233
- // next tag
234
- tagMatch = html.match( startTag );
235
- if (tagMatch) {
236
- nextTag = tagMatch[1];
237
- }
238
- else {
239
- tagMatch = html.match( endTag );
240
- if (tagMatch) {
241
- nextTag = '/' + tagMatch[1];
242
- }
243
- else {
244
- nextTag = '';
245
- }
246
- }
247
-
248
- if ( handler.chars ) {
249
- handler.chars(text, prevTag, nextTag);
250
- }
251
- prevTag = '';
252
-
253
- }
254
- else {
255
-
256
- stackedTag = stack.last().toLowerCase();
257
- reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)<\/' + stackedTag + '[^>]*>', 'i'));
258
-
259
- html = html.replace(reStackedTag, function(all, text) {
260
- if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
261
- text = text
262
- .replace(/<!--([\s\S]*?)-->/g, '$1')
263
- .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
264
- }
265
-
266
- if ( handler.chars ) {
267
- handler.chars( text );
268
- }
269
-
270
- return '';
271
- });
272
-
273
- parseEndTag( '', stackedTag );
274
- }
275
-
276
- if ( html === last ) {
277
- throw 'Parse Error: ' + html;
278
- }
279
- last = html;
280
- }
281
-
282
- // Clean up any remaining tags
283
- parseEndTag();
284
-
285
- function parseStartTag( tag, tagName, rest, unary ) {
286
- var unarySlash = false;
287
-
288
- while ( !handler.html5 && stack.last() && inline[ stack.last() ]) {
289
- parseEndTag( '', stack.last() );
290
- }
291
-
292
- if ( closeSelf[ tagName ] && stack.last() === tagName ) {
293
- parseEndTag( '', tagName );
294
- }
295
-
296
- unary = empty[ tagName ] || !!unary;
297
-
298
- var attrs = [];
299
-
300
- rest.replace(attr, function () {
301
- var name, value, fallbackValue, customOpen, customClose, customAssign, quote;
302
- var ncp = 7; // number of captured parts, scalar
303
-
304
- // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
305
- if (IS_REGEX_CAPTURING_BROKEN && arguments[0].indexOf('""') === -1) {
306
- if (arguments[3] === '') { arguments[3] = undefined; }
307
- if (arguments[4] === '') { arguments[4] = undefined; }
308
- if (arguments[5] === '') { arguments[5] = undefined; }
309
- }
310
-
311
- name = arguments[1];
312
- if ( name ) {
313
- customAssign = arguments[2];
314
- fallbackValue = arguments[3];
315
- value = fallbackValue || arguments[4] || arguments[5];
316
-
317
- if (customAssign) {
318
- quote = arguments[0].charAt(name.length + customAssign.length);
319
- quote = (quote === '\'' || quote === '"') ? quote : '';
320
- }
321
-
322
- }
323
- else if ( handler.customAttrSurround ) {
324
- for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
325
- name = arguments[i * ncp + 7];
326
- customAssign = arguments[i * ncp + 8];
327
- if ( name ) {
328
- fallbackValue = arguments[i * ncp + 9];
329
- value = fallbackValue
330
- || arguments[i * ncp + 10]
331
- || arguments[i * ncp + 11];
332
- customOpen = arguments[i * ncp + 6];
333
- customClose = arguments[i * ncp + 12];
334
- break;
335
- }
336
- }
337
- }
338
-
339
- if ( value === undefined ) {
340
- value = fillAttrs[name] ? name : fallbackValue;
341
- }
342
-
343
- attrs.push({
344
- name: name,
345
- value: value,
346
- customAssign: customAssign || '=',
347
- customOpen: customOpen || '',
348
- customClose: customClose || '',
349
- quote: quote || ''
350
- });
351
- });
352
-
353
- if ( !unary ) {
354
- stack.push( { tag: tagName, attrs: attrs } );
355
- }
356
- else {
357
- unarySlash = tag.match( endingSlash );
358
- }
359
-
360
-
361
- if ( handler.start ) {
362
- handler.start( tagName, attrs, unary, unarySlash );
363
- }
364
- }
365
-
366
- function parseEndTag( tag, tagName ) {
367
- var pos;
368
-
369
- // If no tag name is provided, clean shop
370
- if ( !tagName ) {
371
- pos = 0;
372
- }
373
- else {
374
- // Find the closest opened tag of the same type
375
- var needle = tagName.toLowerCase();
376
- for ( pos = stack.length - 1; pos >= 0; pos-- ) {
377
- if ( stack[ pos ].tag.toLowerCase() === needle ) {
378
- break;
379
- }
380
- }
381
- }
382
-
383
- if ( pos >= 0 ) {
384
- // Close all the open elements, up the stack
385
- for ( var i = stack.length - 1; i >= pos; i-- ) {
386
- if ( handler.end ) {
387
- handler.end( stack[ i ].tag, stack[ i ].attrs );
388
- }
389
- }
390
-
391
- // Remove the open elements from the stack
392
- stack.length = pos;
393
- }
394
- }
395
- };
396
-
397
- global.HTMLtoXML = function( html ) {
398
- var results = '';
399
-
400
- new HTMLParser(html, {
401
- start: function( tag, attrs, unary ) {
402
- results += '<' + tag;
403
-
404
- for ( var i = 0; i < attrs.length; i++ ) {
405
- results += ' ' + attrs[i].name + '="' + (attrs[i].value || '').replace(/"/g, '&#34;') + '"';
406
- }
407
-
408
- results += (unary ? '/' : '') + '>';
409
- },
410
- end: function( tag ) {
411
- results += '</' + tag + '>';
412
- },
413
- chars: function( text ) {
414
- results += text;
415
- },
416
- comment: function( text ) {
417
- results += '<!--' + text + '-->';
418
- },
419
- ignore: function(text) {
420
- results += text;
421
- }
422
- });
423
-
424
- return results;
425
- };
426
-
427
- global.HTMLtoDOM = function( html, doc ) {
428
- // There can be only one of these elements
429
- var one = makeMap('html,head,body,title');
430
-
431
- // Enforce a structure for the document
432
- var structure = {
433
- link: 'head',
434
- base: 'head'
435
- };
436
-
437
- if ( !doc ) {
438
- if ( typeof DOMDocument !== 'undefined' ) {
439
- doc = new DOMDocument();
440
- }
441
- else if ( typeof document !== 'undefined' && document.implementation && document.implementation.createDocument ) {
442
- doc = document.implementation.createDocument('', '', null);
443
- }
444
- else if ( typeof ActiveX !== 'undefined' ) {
445
- doc = new ActiveXObject('Msxml.DOMDocument');
446
- }
447
-
448
- }
449
- else {
450
- doc = doc.ownerDocument ||
451
- doc.getOwnerDocument && doc.getOwnerDocument() ||
452
- doc;
453
- }
454
-
455
- var elems = [],
456
- documentElement = doc.documentElement ||
457
- doc.getDocumentElement && doc.getDocumentElement();
458
-
459
- // If we're dealing with an empty document then we
460
- // need to pre-populate it with the HTML document structure
461
- if ( !documentElement && doc.createElement ) {
462
- (function() {
463
- var html = doc.createElement('html');
464
- var head = doc.createElement('head');
465
- head.appendChild( doc.createElement('title') );
466
- html.appendChild( head );
467
- html.appendChild( doc.createElement('body') );
468
- doc.appendChild( html );
469
- })();
470
- }
471
-
472
- // Find all the unique elements
473
- if ( doc.getElementsByTagName ) {
474
- for ( var i in one ) {
475
- one[ i ] = doc.getElementsByTagName( i )[0];
476
- }
477
- }
478
-
479
- // If we're working with a document, inject contents into
480
- // the body element
481
- var curParentNode = one.body;
482
-
483
- new HTMLParser( html, {
484
- start: function( tagName, attrs, unary ) {
485
- // If it's a pre-built element, then we can ignore
486
- // its construction
487
- if ( one[ tagName ] ) {
488
- curParentNode = one[ tagName ];
489
- return;
490
- }
491
-
492
- var elem = doc.createElement( tagName );
493
-
494
- for ( var attr in attrs ) {
495
- elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
496
- }
497
-
498
- if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] !== 'boolean' ) {
499
- one[ structure[ tagName ] ].appendChild( elem );
500
- }
501
- else if ( curParentNode && curParentNode.appendChild ) {
502
- curParentNode.appendChild( elem );
503
- }
504
-
505
- if ( !unary ) {
506
- elems.push( elem );
507
- curParentNode = elem;
508
- }
509
- },
510
- end: function( /* tag */ ) {
511
- elems.length -= 1;
512
-
513
- // Init the new parentNode
514
- curParentNode = elems[ elems.length - 1 ];
515
- },
516
- chars: function( text ) {
517
- curParentNode.appendChild( doc.createTextNode( text ) );
518
- },
519
- comment: function( /*text*/ ) {
520
- // create comment node
521
- },
522
- ignore: function( /* text */ ) {
523
- // What to do here?
524
- }
525
- });
526
-
527
- return doc;
528
- };
529
-
530
- function makeMap(str) {
531
- var obj = {}, items = str.split(',');
532
- for ( var i = 0; i < items.length; i++ ) {
533
- obj[ items[i] ] = true;
534
- obj[ items[i].toUpperCase() ] = true;
535
- }
536
- return obj;
537
- }
538
- })(typeof exports === 'undefined' ? this : exports);
539
-
540
- /* global CleanCSS */
541
-
542
- (function(global) {
543
- 'use strict';
544
-
545
- var log, HTMLParser;
546
- if (global.console && global.console.log) {
547
- log = function(message) {
548
- // "preserving" `this`
549
- global.console.log(message);
550
- };
551
- }
552
- else {
553
- log = function() {};
554
- }
555
-
556
- if (global.HTMLParser) {
557
- HTMLParser = global.HTMLParser;
558
- }
559
- else if (typeof require === 'function') {
560
- HTMLParser = require('./htmlparser').HTMLParser;
561
- }
562
-
563
- var trimWhitespace = function(str) {
564
- if (typeof str !== 'string') {
565
- return str;
566
- }
567
- return str.replace(/^\s+/, '').replace(/\s+$/, '');
568
- };
569
- if (String.prototype.trim) {
570
- trimWhitespace = function(str) {
571
- if (typeof str !== 'string') {
572
- return str;
573
- }
574
- return str.trim();
575
- };
576
- }
577
-
578
- function collapseWhitespaceAll(str) {
579
- return str ? str.replace(/[\t\n\r ]+/g, ' ') : str;
580
- }
581
-
582
- function createMap(values) {
583
- var map = {};
584
- values.forEach(function(value) {
585
- map[value] = 1;
586
- });
587
- return function(value) {
588
- return map[value] === 1;
589
- };
590
- }
591
-
592
- function createMapFromString(values) {
593
- return createMap(values.split(/,/));
594
- }
595
-
596
- function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
597
- var lineBreakBefore = '', lineBreakAfter = '';
598
-
599
- if (options.preserveLineBreaks) {
600
- str = str.replace(/^[\t ]*[\n\r]+[\t\n\r ]*/, function() {
601
- lineBreakBefore = '\n';
602
- return '';
603
- }).replace(/[\t\n\r ]*[\n\r]+[\t ]*$/, function() {
604
- lineBreakAfter = '\n';
605
- return '';
606
- });
607
- }
608
-
609
- if (trimLeft) {
610
- str = str.replace(/^\s+/, !lineBreakBefore && options.conservativeCollapse ? ' ' : '');
611
- }
612
-
613
- if (trimRight) {
614
- str = str.replace(/\s+$/, !lineBreakAfter && options.conservativeCollapse ? ' ' : '');
615
- }
616
-
617
- if (collapseAll) {
618
- // strip non space whitespace then compress spaces to one
619
- str = collapseWhitespaceAll(str);
620
- }
621
-
622
- return lineBreakBefore + str + lineBreakAfter;
623
- }
624
-
625
- // array of non-empty element tags that will maintain a single space outside of them
626
- var inlineTags = createMapFromString('a,abbr,acronym,b,bdi,bdo,big,button,cite,code,del,dfn,em,font,i,ins,kbd,mark,math,q,rt,rp,s,samp,small,span,strike,strong,sub,sup,svg,time,tt,u,var');
627
- var selfClosingInlineTags = createMapFromString('comment,img,input');
628
-
629
- function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
630
- var trimLeft = prevTag && !selfClosingInlineTags(prevTag) &&
631
- (options.collapseInlineTagWhitespace || prevTag.charAt(0) !== '/' || !inlineTags(prevTag.substr(1)));
632
- var trimRight = nextTag && !selfClosingInlineTags(nextTag) &&
633
- (options.collapseInlineTagWhitespace || nextTag.charAt(0) === '/' || !inlineTags(nextTag));
634
- return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
635
- }
636
-
637
- function isConditionalComment(text) {
638
- return ((/\[if[^\]]+\]/).test(text) || (/\s*((?:<!)?\[endif\])$/).test(text));
639
- }
640
-
641
- function isIgnoredComment(text, options) {
642
- if (/^!/.test(text)) {
643
- return true;
644
- }
645
-
646
- if (options.ignoreCustomComments) {
647
- for (var i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
648
- if (options.ignoreCustomComments[i].test(text)) {
649
- return true;
650
- }
651
- }
652
- }
653
-
654
- return false;
655
- }
656
-
657
- function isEventAttribute(attrName, options) {
658
- var patterns = options.customEventAttributes;
659
- if (patterns) {
660
- for (var i = patterns.length; i--;) {
661
- if (patterns[i].test(attrName)) {
662
- return true;
663
- }
664
- }
665
- return false;
666
- }
667
- else {
668
- return (/^on[a-z]{3,}$/).test(attrName);
669
- }
670
- }
671
-
672
- function canRemoveAttributeQuotes(value) {
673
- // http://mathiasbynens.be/notes/unquoted-attribute-values
674
- return (/^[^\x20\t\n\f\r"'`=<>]+$/).test(value);
675
- }
676
-
677
- function attributesInclude(attributes, attribute) {
678
- for (var i = attributes.length; i--;) {
679
- if (attributes[i].name.toLowerCase() === attribute) {
680
- return true;
681
- }
682
- }
683
- return false;
684
- }
685
-
686
- function isAttributeRedundant(tag, attrName, attrValue, attrs) {
687
- attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
688
-
689
- return (
690
- (tag === 'script' &&
691
- attrName === 'language' &&
692
- attrValue === 'javascript') ||
693
-
694
- (tag === 'form' &&
695
- attrName === 'method' &&
696
- attrValue === 'get') ||
697
-
698
- (tag === 'input' &&
699
- attrName === 'type' &&
700
- attrValue === 'text') ||
701
-
702
- (tag === 'script' &&
703
- attrName === 'charset' &&
704
- !attributesInclude(attrs, 'src')) ||
705
-
706
- (tag === 'a' &&
707
- attrName === 'name' &&
708
- attributesInclude(attrs, 'id')) ||
709
-
710
- (tag === 'area' &&
711
- attrName === 'shape' &&
712
- attrValue === 'rect')
713
- );
714
- }
715
-
716
- function isScriptTypeAttribute(tag, attrName, attrValue) {
717
- return (
718
- tag === 'script' &&
719
- attrName === 'type' &&
720
- trimWhitespace(attrValue.toLowerCase()) === 'text/javascript'
721
- );
722
- }
723
-
724
- // https://mathiasbynens.be/demo/javascript-mime-type
725
- // https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
726
- var executableScriptsMimetypes = createMap([
727
- 'text/javascript',
728
- 'text/ecmascript',
729
- 'text/jscript',
730
- 'application/javascript',
731
- 'application/x-javascript',
732
- 'application/ecmascript'
733
- ]);
734
-
735
- function isExecutableScript(tag, attrs) {
736
- if (tag !== 'script') {
737
- return false;
738
- }
739
- for (var i = 0, len = attrs.length; i < len; i++) {
740
- var attrName = attrs[i].name.toLowerCase();
741
- if (attrName === 'type') {
742
- var attrValue = trimWhitespace(attrs[i].value).split(/;/, 2)[0].toLowerCase();
743
- return attrValue === '' || executableScriptsMimetypes(attrValue);
744
- }
745
- }
746
- return true;
747
- }
748
-
749
- function isStyleLinkTypeAttribute(tag, attrName, attrValue) {
750
- return (
751
- (tag === 'style' || tag === 'link') &&
752
- attrName === 'type' &&
753
- trimWhitespace(attrValue.toLowerCase()) === 'text/css'
754
- );
755
- }
756
-
757
- var enumeratedAttributeValues = {
758
- draggable: ['true', 'false'] // defaults to 'auto'
759
- };
760
-
761
- function isBooleanAttribute(attrName, attrValue) {
762
- var isSimpleBoolean = (/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|truespeed|typemustmatch|visible)$/i).test(attrName);
763
- if (isSimpleBoolean) {
764
- return true;
765
- }
766
-
767
- var attrValueEnumeration = enumeratedAttributeValues[attrName.toLowerCase()];
768
- if (!attrValueEnumeration) {
769
- return false;
770
- }
771
- else {
772
- return (-1 === attrValueEnumeration.indexOf(attrValue.toLowerCase()));
773
- }
774
- }
775
-
776
- function isUriTypeAttribute(attrName, tag) {
777
- return (
778
- ((/^(?:a|area|link|base)$/).test(tag) && attrName === 'href') ||
779
- (tag === 'img' && (/^(?:src|longdesc|usemap)$/).test(attrName)) ||
780
- (tag === 'object' && (/^(?:classid|codebase|data|usemap)$/).test(attrName)) ||
781
- (tag === 'q' && attrName === 'cite') ||
782
- (tag === 'blockquote' && attrName === 'cite') ||
783
- ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
784
- (tag === 'form' && attrName === 'action') ||
785
- (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
786
- (tag === 'head' && attrName === 'profile') ||
787
- (tag === 'script' && (attrName === 'src' || attrName === 'for'))
788
- );
789
- }
790
-
791
- function isNumberTypeAttribute(attrName, tag) {
792
- return (
793
- ((/^(?:a|area|object|button)$/).test(tag) && attrName === 'tabindex') ||
794
- (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
795
- (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
796
- (tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
797
- (tag === 'colgroup' && attrName === 'span') ||
798
- (tag === 'col' && attrName === 'span') ||
799
- ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
800
- );
801
- }
802
-
803
- function isCanonicalURL(tag, attrs) {
804
- if (tag !== 'link') {
805
- return false;
806
- }
807
- for (var i = 0, len = attrs.length; i < len; i++) {
808
- if (attrs[i].name === 'rel' && attrs[i].value === 'canonical') {
809
- return true;
810
- }
811
- }
812
- }
813
-
814
- var fnPrefix = '!function(){';
815
- var fnSuffix = '}();';
816
-
817
- function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
818
- if (attrValue && isEventAttribute(attrName, options)) {
819
- attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '').replace(/\s*;$/, '');
820
- if (options.minifyJS) {
821
- var minified = minifyJS(fnPrefix + attrValue + fnSuffix, options.minifyJS);
822
- return minified.slice(fnPrefix.length, -fnSuffix.length);
823
- }
824
- return attrValue;
825
- }
826
- else if (attrName === 'class') {
827
- return collapseWhitespaceAll(trimWhitespace(attrValue));
828
- }
829
- else if (isUriTypeAttribute(attrName, tag)) {
830
- attrValue = trimWhitespace(attrValue);
831
- if (options.minifyURLs && !isCanonicalURL(tag, attrs)) {
832
- return minifyURLs(attrValue, options.minifyURLs);
833
- }
834
- return attrValue;
835
- }
836
- else if (isNumberTypeAttribute(attrName, tag)) {
837
- return trimWhitespace(attrValue);
838
- }
839
- else if (attrName === 'style') {
840
- attrValue = trimWhitespace(attrValue);
841
- if (attrValue) {
842
- attrValue = attrValue.replace(/\s*;\s*$/, '');
843
- }
844
- if (options.minifyCSS) {
845
- return minifyCSS(attrValue, options.minifyCSS, true);
846
- }
847
- return attrValue;
848
- }
849
- else if (isMetaViewport(tag, attrs) && attrName === 'content') {
850
- attrValue = attrValue.replace(/\s+/g, '').replace(/[0-9]+\.[0-9]+/g, function(numString) {
851
- // "0.90000" -> "0.9"
852
- // "1.0" -> "1"
853
- // "1.0001" -> "1.0001" (unchanged)
854
- return (+numString).toString();
855
- });
856
- }
857
- else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
858
- attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
859
- }
860
- return attrValue;
861
- }
862
-
863
- function isMetaViewport(tag, attrs) {
864
- if (tag !== 'meta') {
865
- return false;
866
- }
867
- for (var i = 0, len = attrs.length; i < len; i++) {
868
- if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
869
- return true;
870
- }
871
- }
872
- }
873
-
874
- // Wrap CSS declarations for CleanCSS > 3.x
875
- // See https://github.com/jakubpawlowicz/clean-css/issues/418
876
- function wrapCSS(text) {
877
- return '*{' + text + '}';
878
- }
879
-
880
- function unwrapCSS(text) {
881
- var matches = text.match(/^\*\{([\s\S]*)\}$/m);
882
- if (matches && matches[1]) {
883
- return matches[1];
884
- }
885
- else {
886
- return text;
887
- }
888
- }
889
-
890
- function cleanConditionalComment(comment) {
891
- return comment
892
- .replace(/^(\[[^\]]+\]>)\s*/, '$1')
893
- .replace(/\s*(<!\[endif\])$/, '$1');
894
- }
895
-
896
- function removeCDATASections(text) {
897
- return text
898
- // "/* <![CDATA[ */" or "// <![CDATA["
899
- .replace(/^(?:\s*\/\*\s*<!\[CDATA\[\s*\*\/|\s*\/\/\s*<!\[CDATA\[.*)/, '')
900
- // "/* ]]> */" or "// ]]>"
901
- .replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/, '');
902
- }
903
-
904
- function processScript(text, options, currentAttrs) {
905
- for (var i = 0, len = currentAttrs.length; i < len; i++) {
906
- if (currentAttrs[i].name.toLowerCase() === 'type' &&
907
- options.processScripts.indexOf(currentAttrs[i].value) > -1) {
908
- return minify(text, options);
909
- }
910
- }
911
- return text;
912
- }
913
-
914
- var reStartDelimiter = {
915
- // account for js + html comments (e.g.: //<!--)
916
- script: /^\s*(?:\/\/)?\s*<!--.*\n?/,
917
- style: /^\s*<!--\s*/
918
- };
919
- var reEndDelimiter = {
920
- script: /\s*(?:\/\/)?\s*-->\s*$/,
921
- style: /\s*-->\s*$/
922
- };
923
- function removeComments(text, tag) {
924
- return text.replace(reStartDelimiter[tag], '').replace(reEndDelimiter[tag], '');
925
- }
926
-
927
- // Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
928
- // with the following deviations:
929
- // - retain <body> if followed by <noscript>
930
- // - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow http://www.w3.org/TR/html5/syntax.html#optional-tags
931
- var optionalStartTags = createMapFromString('html,head,body,colgroup,tbody');
932
- var optionalEndTags = createMapFromString('html,head,body,li,dt,dd,p,rb,rt,rtc,rp,optgroup,option,colgroup,caption,thead,tbody,tfoot,tr,td,th');
933
- var headerTags = createMapFromString('meta,link,script,style,template,noscript');
934
- var descriptionTags = createMapFromString('dt,dd');
935
- var pBlockTags = createMapFromString('address,article,aside,blockquote,details,div,dl,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,menu,nav,ol,p,pre,section,table,ul');
936
- var pInlineTags = createMapFromString('a,audio,del,ins,map,noscript,video');
937
- var rubyTags = createMapFromString('rb,rt,rtc,rp');
938
- var rtcTag = createMapFromString('rb,rtc,rp');
939
- var optionTag = createMapFromString('option,optgroup');
940
- var tableContentTags = createMapFromString('tbody,tfoot');
941
- var tableSectionTags = createMapFromString('thead,tbody,tfoot');
942
- var cellTags = createMapFromString('td,th');
943
- var topLevelTags = createMapFromString('html,head,body');
944
- var compactTags = createMapFromString('html,body');
945
- var looseTags = createMapFromString('head,colgroup,caption');
946
-
947
- function canRemoveParentTag(optionalStartTag, tag) {
948
- switch (optionalStartTag) {
949
- case 'html':
950
- case 'head':
951
- return true;
952
- case 'body':
953
- return !headerTags(tag);
954
- case 'colgroup':
955
- return tag === 'col';
956
- case 'tbody':
957
- return tag === 'tr';
958
- }
959
- return false;
960
- }
961
-
962
- function isStartTagMandatory(optionalEndTag, tag) {
963
- switch (tag) {
964
- case 'colgroup':
965
- return optionalEndTag === 'colgroup';
966
- case 'tbody':
967
- return tableSectionTags(optionalEndTag);
968
- }
969
- return false;
970
- }
971
-
972
- function canRemovePrecedingTag(optionalEndTag, tag) {
973
- switch (optionalEndTag) {
974
- case 'html':
975
- case 'head':
976
- case 'body':
977
- case 'colgroup':
978
- case 'caption':
979
- return true;
980
- case 'li':
981
- case 'optgroup':
982
- case 'tr':
983
- return tag === optionalEndTag;
984
- case 'dt':
985
- case 'dd':
986
- return descriptionTags(tag);
987
- case 'p':
988
- return pBlockTags(tag);
989
- case 'rb':
990
- case 'rt':
991
- case 'rp':
992
- return rubyTags(tag);
993
- case 'rtc':
994
- return rtcTag(tag);
995
- case 'option':
996
- return optionTag(tag);
997
- case 'thead':
998
- case 'tbody':
999
- return tableContentTags(tag);
1000
- case 'tfoot':
1001
- return tag === 'tbody';
1002
- case 'td':
1003
- case 'th':
1004
- return cellTags(tag);
1005
- }
1006
- return false;
1007
- }
1008
-
1009
- var reEmptyAttribute = new RegExp(
1010
- '^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
1011
- '?:down|up|over|move|out)|key(?:press|down|up)))$');
1012
-
1013
- function canDeleteEmptyAttribute(tag, attrName, attrValue) {
1014
- var isValueEmpty = !attrValue || (/^\s*$/).test(attrValue);
1015
- if (isValueEmpty) {
1016
- return (
1017
- (tag === 'input' && attrName === 'value') ||
1018
- reEmptyAttribute.test(attrName));
1019
- }
1020
- return false;
1021
- }
1022
-
1023
- function canRemoveElement(tag, attrs) {
1024
- if (tag === 'textarea') {
1025
- return false;
1026
- }
1027
-
1028
- if (tag === 'script') {
1029
- for (var i = attrs.length - 1; i >= 0; i--) {
1030
- if (attrs[i].name === 'src') {
1031
- return false;
1032
- }
1033
- }
1034
- }
1035
-
1036
- return true;
1037
- }
1038
-
1039
- function canCollapseWhitespace(tag) {
1040
- return !(/^(?:script|style|pre|textarea)$/.test(tag));
1041
- }
1042
-
1043
- function canTrimWhitespace(tag) {
1044
- return !(/^(?:pre|textarea)$/.test(tag));
1045
- }
1046
-
1047
- function normalizeAttribute(attr, attrs, tag, hasUnarySlash, index, options, isLast) {
1048
-
1049
- var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
1050
- attrValue = attr.value,
1051
- attrQuote = attr.quote,
1052
- attrFragment,
1053
- emittedAttrValue;
1054
-
1055
- if ((options.removeRedundantAttributes &&
1056
- isAttributeRedundant(tag, attrName, attrValue, attrs))
1057
- ||
1058
- (options.removeScriptTypeAttributes &&
1059
- isScriptTypeAttribute(tag, attrName, attrValue))
1060
- ||
1061
- (options.removeStyleLinkTypeAttributes &&
1062
- isStyleLinkTypeAttribute(tag, attrName, attrValue))) {
1063
- return '';
1064
- }
1065
-
1066
- attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
1067
-
1068
- if (options.removeEmptyAttributes &&
1069
- canDeleteEmptyAttribute(tag, attrName, attrValue)) {
1070
- return '';
1071
- }
1072
-
1073
- if (attrValue !== undefined && !options.removeAttributeQuotes ||
1074
- !canRemoveAttributeQuotes(attrValue)) {
1075
- if (!options.preventAttributesEscaping) {
1076
- if (options.quoteCharacter !== undefined) {
1077
- attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
1078
- }
1079
- else {
1080
- var apos = (attrValue.match(/'/g) || []).length;
1081
- var quot = (attrValue.match(/"/g) || []).length;
1082
- attrQuote = apos < quot ? '\'' : '"';
1083
- }
1084
- if (attrQuote === '"') {
1085
- attrValue = attrValue.replace(/"/g, '&#34;');
1086
- }
1087
- else {
1088
- attrValue = attrValue.replace(/'/g, '&#39;');
1089
- }
1090
- }
1091
- emittedAttrValue = attrQuote + attrValue + attrQuote;
1092
- if (!isLast && !options.removeTagWhitespace) {
1093
- emittedAttrValue += ' ';
1094
- }
1095
- }
1096
- // make sure trailing slash is not interpreted as HTML self-closing tag
1097
- else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) {
1098
- emittedAttrValue = attrValue;
1099
- }
1100
- else {
1101
- emittedAttrValue = attrValue + ' ';
1102
- }
1103
-
1104
- if (attrValue === undefined || (options.collapseBooleanAttributes &&
1105
- isBooleanAttribute(attrName, attrValue))) {
1106
- attrFragment = attrName;
1107
- if (!isLast) {
1108
- attrFragment += ' ';
1109
- }
1110
- }
1111
- else {
1112
- attrFragment = attrName + attr.customAssign + emittedAttrValue;
1113
- }
1114
-
1115
- return attr.customOpen + attrFragment + attr.customClose;
1116
- }
1117
-
1118
- function setDefaultTesters(options) {
1119
-
1120
- var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];
1121
-
1122
- for (var i = 0, len = defaultTesters.length; i < len; i++) {
1123
- if (!options[defaultTesters[i]]) {
1124
- options[defaultTesters[i]] = function() {
1125
- return false;
1126
- };
1127
- }
1128
- }
1129
- }
1130
-
1131
- function minifyURLs(text, options) {
1132
- if (typeof options !== 'object') {
1133
- options = { };
1134
- }
1135
-
1136
- try {
1137
- // try to get global reference first
1138
- var __RelateUrl = global.RelateUrl;
1139
-
1140
- if (typeof __RelateUrl === 'undefined' && typeof require === 'function') {
1141
- __RelateUrl = require('relateurl');
1142
- }
1143
-
1144
- // noop
1145
- if (!__RelateUrl) {
1146
- return text;
1147
- }
1148
-
1149
- if (__RelateUrl.relate) {
1150
- return __RelateUrl.relate(text, options);
1151
- }
1152
- else {
1153
- return text;
1154
- }
1155
- }
1156
- catch (err) {
1157
- log(err);
1158
- }
1159
- return text;
1160
- }
1161
-
1162
- function minifyJS(text, options) {
1163
- if (typeof options !== 'object') {
1164
- options = { };
1165
- }
1166
- options.fromString = true;
1167
- var outputOptions = options.output || {};
1168
- outputOptions.inline_script = true;
1169
- options.output = outputOptions;
1170
-
1171
- try {
1172
- // try to get global reference first
1173
- var __UglifyJS = global.UglifyJS;
1174
-
1175
- if (typeof __UglifyJS === 'undefined' && typeof require === 'function') {
1176
- __UglifyJS = require('uglify-js');
1177
- }
1178
-
1179
- // noop
1180
- if (!__UglifyJS) {
1181
- return text;
1182
- }
1183
-
1184
- if (__UglifyJS.minify) {
1185
- return __UglifyJS.minify(text, options).code;
1186
- }
1187
- else if (__UglifyJS.parse) {
1188
-
1189
- var ast = __UglifyJS.parse(text);
1190
- ast.figure_out_scope();
1191
-
1192
- var compressor = __UglifyJS.Compressor();
1193
- var compressedAst = ast.transform(compressor);
1194
-
1195
- compressedAst.figure_out_scope();
1196
- compressedAst.compute_char_frequency();
1197
-
1198
- if (options.mangle !== false) {
1199
- compressedAst.mangle_names();
1200
- }
1201
-
1202
- var stream = __UglifyJS.OutputStream(options.output);
1203
- compressedAst.print(stream);
1204
-
1205
- return stream.toString();
1206
- }
1207
- else {
1208
- return text;
1209
- }
1210
- }
1211
- catch (err) {
1212
- log(err);
1213
- }
1214
- return text;
1215
- }
1216
-
1217
- function minifyCSS(text, options, inline) {
1218
- if (typeof options !== 'object') {
1219
- options = { };
1220
- }
1221
- if (typeof options.advanced === 'undefined') {
1222
- options.advanced = false;
1223
- }
1224
- try {
1225
- var cleanCSS;
1226
-
1227
- if (typeof CleanCSS !== 'undefined') {
1228
- cleanCSS = new CleanCSS(options);
1229
- }
1230
- else if (typeof require === 'function') {
1231
- var CleanCSSModule = require('clean-css');
1232
- cleanCSS = new CleanCSSModule(options);
1233
- }
1234
- if (inline) {
1235
- return unwrapCSS(cleanCSS.minify(wrapCSS(text)).styles);
1236
- }
1237
- else {
1238
- return cleanCSS.minify(text).styles;
1239
- }
1240
- }
1241
- catch (err) {
1242
- log(err);
1243
- }
1244
- return text;
1245
- }
1246
-
1247
- function uniqueId(value) {
1248
- var id;
1249
- do {
1250
- id = Math.random().toString(36).slice(2);
1251
- } while (~value.indexOf(id));
1252
- return id;
1253
- }
1254
-
1255
- function minify(value, options) {
1256
-
1257
- options = options || {};
1258
- var optionsStack = [];
1259
-
1260
- value = trimWhitespace(value);
1261
- setDefaultTesters(options);
1262
-
1263
- var results = [ ],
1264
- buffer = [ ],
1265
- charsPrevTag,
1266
- currentChars = '',
1267
- currentTag = '',
1268
- currentAttrs = [],
1269
- stackNoTrimWhitespace = [],
1270
- stackNoCollapseWhitespace = [],
1271
- optionalStartTag = '',
1272
- optionalEndTag = '',
1273
- lint = options.lint,
1274
- t = Date.now(),
1275
- ignoredMarkupChunks = [ ],
1276
- ignoredCustomMarkupChunks = [ ],
1277
- uidIgnore,
1278
- uidAttr;
1279
-
1280
- if (~value.indexOf('<!-- htmlmin:ignore -->')) {
1281
- uidIgnore = '<!--!' + uniqueId(value) + '-->';
1282
- // temporarily replace ignored chunks with comments,
1283
- // so that we don't have to worry what's there.
1284
- // for all we care there might be
1285
- // completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
1286
- value = value.replace(/<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g, function(match, group1) {
1287
- ignoredMarkupChunks.push(group1);
1288
- return uidIgnore;
1289
- });
1290
- }
1291
-
1292
- var customFragments = (options.ignoreCustomFragments || [
1293
- /<%[\s\S]*?%>/,
1294
- /<\?[\s\S]*?\?>/
1295
- ]).map(function(re) {
1296
- return re.source;
1297
- });
1298
- if (customFragments.length) {
1299
- var reCustomIgnore = new RegExp('\\s*(?:' + customFragments.join('|') + ')\\s*', 'g');
1300
- // temporarily replace custom ignored fragments with unique attributes
1301
- value = value.replace(reCustomIgnore, function(match) {
1302
- if (!uidAttr) {
1303
- uidAttr = uniqueId(value);
1304
- }
1305
- ignoredCustomMarkupChunks.push(match);
1306
- return ' ' + uidAttr + ' ';
1307
- });
1308
- }
1309
-
1310
- function _canCollapseWhitespace(tag, attrs) {
1311
- return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
1312
- }
1313
-
1314
- function _canTrimWhitespace(tag, attrs) {
1315
- return canTrimWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
1316
- }
1317
-
1318
- function removeStartTag() {
1319
- var index = buffer.length - 1;
1320
- while (index > 0 && !/^<[^/!]/.test(buffer[index])) {
1321
- index--;
1322
- }
1323
- buffer.length = Math.max(0, index);
1324
- }
1325
-
1326
- function removeEndTag() {
1327
- var index = buffer.length - 1;
1328
- while (index > 0 && !/^<\//.test(buffer[index])) {
1329
- index--;
1330
- }
1331
- buffer.length = index;
1332
- }
1333
-
1334
- new HTMLParser(value, {
1335
- html5: typeof options.html5 !== 'undefined' ? options.html5 : true,
1336
-
1337
- start: function(tag, attrs, unary, unarySlash) {
1338
- var lowerTag = tag.toLowerCase();
1339
-
1340
- if (lowerTag === 'svg') {
1341
- optionsStack.push(options);
1342
- var nextOptions = {};
1343
- for (var key in options) {
1344
- nextOptions[key] = options[key];
1345
- }
1346
- nextOptions.keepClosingSlash = true;
1347
- nextOptions.caseSensitive = true;
1348
- options = nextOptions;
1349
- }
1350
-
1351
- tag = options.caseSensitive ? tag : lowerTag;
1352
-
1353
- currentTag = tag;
1354
- charsPrevTag = tag;
1355
- currentChars = '';
1356
- currentAttrs = attrs;
1357
-
1358
- var optional = options.removeOptionalTags;
1359
- if (optional) {
1360
- // <html> may be omitted if first thing inside is not comment
1361
- // <head> may be omitted if first thing inside is an element
1362
- // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
1363
- // <colgroup> may be omitted if first thing inside is <col>
1364
- // <tbody> may be omitted if first thing inside is <tr>
1365
- if (canRemoveParentTag(optionalStartTag, tag)) {
1366
- removeStartTag();
1367
- }
1368
- optionalStartTag = '';
1369
- // end-tag-followed-by-start-tag omission rules
1370
- if (canRemovePrecedingTag(optionalEndTag, tag)) {
1371
- removeEndTag();
1372
- // <colgroup> cannot be omitted if preceding </colgroup> is omitted
1373
- // <tbody> cannot be omitted if preceding </tbody>, </thead> or </tfoot> is omitted
1374
- optional = !isStartTagMandatory(optionalEndTag, tag);
1375
- }
1376
- optionalEndTag = '';
1377
- }
1378
-
1379
- // set whitespace flags for nested tags (eg. <code> within a <pre>)
1380
- if (options.collapseWhitespace) {
1381
- if (!_canTrimWhitespace(tag, attrs)) {
1382
- stackNoTrimWhitespace.push(tag);
1383
- }
1384
- if (!_canCollapseWhitespace(tag, attrs)) {
1385
- stackNoCollapseWhitespace.push(tag);
1386
- }
1387
- }
1388
-
1389
- var openTag = '<' + tag;
1390
- var hasUnarySlash = unarySlash && options.keepClosingSlash;
1391
-
1392
- buffer.push(openTag);
1393
-
1394
- if (lint) {
1395
- lint.testElement(tag);
1396
- }
1397
-
1398
- var parts = [ ];
1399
- var token, isLast = true;
1400
- for (var i = attrs.length; --i >= 0; ) {
1401
- if (lint) {
1402
- lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].value);
1403
- }
1404
- token = normalizeAttribute(attrs[i], attrs, tag, hasUnarySlash, i, options, isLast);
1405
- if (token) {
1406
- isLast = false;
1407
- parts.unshift(token);
1408
- }
1409
- }
1410
- if (parts.length > 0) {
1411
- buffer.push(' ');
1412
- buffer.push.apply(buffer, parts);
1413
- }
1414
- // start tag must never be omitted if it has any attributes
1415
- else if (optional && optionalStartTags(tag)) {
1416
- optionalStartTag = tag;
1417
- }
1418
-
1419
- buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
1420
- },
1421
- end: function(tag, attrs) {
1422
- var lowerTag = tag.toLowerCase();
1423
- if (lowerTag === 'svg') {
1424
- options = optionsStack.pop();
1425
- }
1426
-
1427
- tag = options.caseSensitive ? tag : lowerTag;
1428
-
1429
- if (options.removeOptionalTags) {
1430
- // </html> or </body> may be omitted if not followed by comment
1431
- // </head> may be omitted if not followed by space or comment
1432
- // </p> may be omitted if no more content in non-</a> parent
1433
- // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1434
- if (optionalEndTag && optionalEndTag !== 'dt' && optionalEndTag !== 'thead' && (optionalEndTag !== 'p' || !pInlineTags(tag))) {
1435
- removeEndTag();
1436
- }
1437
- optionalEndTag = optionalEndTags(tag) ? tag : '';
1438
- }
1439
-
1440
- // check if current tag is in a whitespace stack
1441
- if (options.collapseWhitespace) {
1442
- if (stackNoTrimWhitespace.length) {
1443
- if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
1444
- stackNoTrimWhitespace.pop();
1445
- }
1446
- }
1447
- else {
1448
- var charsIndex;
1449
- if (buffer.length > 1 && buffer[buffer.length - 1] === '' && /\s+$/.test(buffer[buffer.length - 2])) {
1450
- charsIndex = buffer.length - 2;
1451
- }
1452
- else if (buffer.length > 0 && /\s+$/.test(buffer[buffer.length - 1])) {
1453
- charsIndex = buffer.length - 1;
1454
- }
1455
- if (charsIndex > 0) {
1456
- buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(text) {
1457
- return collapseWhitespaceSmart(text, 'comment', '/' + tag, options);
1458
- });
1459
- }
1460
- }
1461
- if (stackNoCollapseWhitespace.length &&
1462
- tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
1463
- stackNoCollapseWhitespace.pop();
1464
- }
1465
- }
1466
-
1467
- var isElementEmpty = false;
1468
- if (tag === currentTag) {
1469
- currentTag = '';
1470
- isElementEmpty = currentChars === '';
1471
- }
1472
- if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
1473
- // remove last "element" from buffer
1474
- removeStartTag();
1475
- optionalStartTag = '';
1476
- optionalEndTag = '';
1477
- }
1478
- else {
1479
- // push out everything but the end tag
1480
- results.push.apply(results, buffer);
1481
- buffer = ['</' + tag + '>'];
1482
- charsPrevTag = '/' + tag;
1483
- currentChars = '';
1484
- }
1485
- },
1486
- chars: function(text, prevTag, nextTag) {
1487
- prevTag = prevTag === '' ? 'comment' : prevTag;
1488
- nextTag = nextTag === '' ? 'comment' : nextTag;
1489
- if (options.collapseWhitespace) {
1490
- if (!stackNoTrimWhitespace.length) {
1491
- if (prevTag === 'comment') {
1492
- var removed = buffer[buffer.length - 1] === '';
1493
- if (removed) {
1494
- prevTag = charsPrevTag;
1495
- }
1496
- if (buffer.length > 1 && (removed || currentChars.charAt(currentChars.length - 1) === ' ')) {
1497
- var charsIndex = buffer.length - 2;
1498
- buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(trailingSpaces) {
1499
- text = trailingSpaces + text;
1500
- return '';
1501
- });
1502
- }
1503
- }
1504
- text = prevTag || nextTag ? collapseWhitespaceSmart(text, prevTag, nextTag, options) : trimWhitespace(text);
1505
- }
1506
- if (!stackNoCollapseWhitespace.length) {
1507
- text = prevTag && nextTag || nextTag === 'html' ? text : collapseWhitespaceAll(text);
1508
- }
1509
- }
1510
- if (currentTag === 'script' || currentTag === 'style') {
1511
- if (options.removeCommentsFromCDATA) {
1512
- text = removeComments(text, currentTag);
1513
- }
1514
- if (options.removeCDATASectionsFromCDATA) {
1515
- text = removeCDATASections(text);
1516
- }
1517
- if (options.processScripts) {
1518
- text = processScript(text, options, currentAttrs);
1519
- }
1520
- }
1521
- if (options.minifyJS && isExecutableScript(currentTag, currentAttrs)) {
1522
- text = minifyJS(text, options.minifyJS);
1523
- if (text.charAt(text.length - 1) === ';') {
1524
- text = text.slice(0, -1);
1525
- }
1526
- }
1527
- if (currentTag === 'style' && options.minifyCSS) {
1528
- text = minifyCSS(text, options.minifyCSS);
1529
- }
1530
- if (options.removeOptionalTags && text) {
1531
- // <html> may be omitted if first thing inside is not comment
1532
- // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
1533
- if (optionalStartTag === 'html' || optionalStartTag === 'body' && !/^\s/.test(text)) {
1534
- removeStartTag();
1535
- }
1536
- optionalStartTag = '';
1537
- // </html> or </body> may be omitted if not followed by comment
1538
- // </head>, </colgroup> or </caption> may be omitted if not followed by space or comment
1539
- if (compactTags(optionalEndTag) || looseTags(optionalEndTag) && !/^\s/.test(text)) {
1540
- removeEndTag();
1541
- }
1542
- optionalEndTag = '';
1543
- }
1544
- charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
1545
- currentChars += text;
1546
- if (lint) {
1547
- lint.testChars(text);
1548
- }
1549
- buffer.push(text);
1550
- },
1551
- comment: function(text, nonStandard) {
1552
- var prefix = nonStandard ? '<!' : '<!--';
1553
- var suffix = nonStandard ? '>' : '-->';
1554
- if (options.removeComments) {
1555
- if (isConditionalComment(text)) {
1556
- text = prefix + cleanConditionalComment(text) + suffix;
1557
- }
1558
- else if (isIgnoredComment(text, options)) {
1559
- text = '<!--' + text + '-->';
1560
- }
1561
- else {
1562
- text = '';
1563
- }
1564
- }
1565
- else {
1566
- text = prefix + text + suffix;
1567
- }
1568
- if (options.removeOptionalTags && text) {
1569
- // preceding comments suppress tag omissions
1570
- optionalStartTag = '';
1571
- optionalEndTag = '';
1572
- }
1573
- buffer.push(text);
1574
- },
1575
- doctype: function(doctype) {
1576
- buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespaceAll(doctype));
1577
- },
1578
- customAttrAssign: options.customAttrAssign,
1579
- customAttrSurround: options.customAttrSurround
1580
- });
1581
-
1582
- if (options.removeOptionalTags) {
1583
- // <html> may be omitted if first thing inside is not comment
1584
- // <head> or <body> may be omitted if empty
1585
- if (topLevelTags(optionalStartTag)) {
1586
- removeStartTag();
1587
- }
1588
- // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
1589
- if (optionalEndTag && optionalEndTag !== 'dt' && optionalEndTag !== 'thead') {
1590
- removeEndTag();
1591
- }
1592
- }
1593
-
1594
- results.push.apply(results, buffer);
1595
- var str = joinResultSegments(results, options);
1596
-
1597
- if (uidAttr) {
1598
- str = str.replace(new RegExp('(\\s*)' + uidAttr + '(\\s*)', 'g'), function(match, prefix, suffix) {
1599
- var chunk = ignoredCustomMarkupChunks.shift();
1600
- return options.collapseWhitespace ? collapseWhitespace(prefix + chunk + suffix, {
1601
- preserveLineBreaks: options.preserveLineBreaks,
1602
- conservativeCollapse: true
1603
- }, true, true) : chunk;
1604
- });
1605
- }
1606
- if (uidIgnore) {
1607
- str = str.replace(new RegExp(uidIgnore, 'g'), function() {
1608
- return ignoredMarkupChunks.shift();
1609
- });
1610
- }
1611
-
1612
- log('minified in: ' + (Date.now() - t) + 'ms');
1613
- return str;
1614
- }
1615
-
1616
- function joinResultSegments(results, options) {
1617
- var str;
1618
- var maxLineLength = options.maxLineLength;
1619
- if (maxLineLength) {
1620
- var token;
1621
- var lines = [];
1622
- var line = '';
1623
- for (var i = 0, len = results.length; i < len; i++) {
1624
- token = results[i];
1625
- if (line.length + token.length < maxLineLength) {
1626
- line += token;
1627
- }
1628
- else {
1629
- lines.push(line.replace(/^\n/, ''));
1630
- line = token;
1631
- }
1632
- }
1633
- lines.push(line);
1634
-
1635
- str = lines.join('\n');
1636
- }
1637
- else {
1638
- str = results.join('');
1639
- }
1640
-
1641
- return trimWhitespace(str);
1642
- }
1643
-
1644
- // for CommonJS enviroments, export everything
1645
- if (typeof exports !== 'undefined') {
1646
- exports.minify = minify;
1647
- }
1648
- else {
1649
- global.minify = minify;
1650
- }
1651
-
1652
- }(typeof exports === 'undefined' ? this : exports));
1653
-
1654
- /*!
1655
- * HTMLLint (to be used in conjunction with HTMLMinifier)
1656
- *
1657
- * Copyright (c) 2010-2013 Juriy "kangax" Zaytsev
1658
- * Licensed under the MIT license.
1659
- *
1660
- */
1661
-
1662
- (function(global) {
1663
- 'use strict';
1664
-
1665
- function isPresentationalElement(tag) {
1666
- return (/^(?:big|small|hr|blink|marquee)$/).test(tag);
1667
- }
1668
- function isDeprecatedElement(tag) {
1669
- return (/^(?:applet|basefont|center|dir|font|isindex|strike)$/).test(tag);
1670
- }
1671
- function isEventAttribute(attrName) {
1672
- return (/^on[a-z]+/).test(attrName);
1673
- }
1674
- function isStyleAttribute(attrName) {
1675
- return (attrName.toLowerCase() === 'style');
1676
- }
1677
- function isDeprecatedAttribute(tag, attrName) {
1678
- return (
1679
- (attrName === 'align' &&
1680
- (/^(?:caption|applet|iframe|img|imput|object|legend|table|hr|div|h[1-6]|p)$/).test(tag)) ||
1681
- (attrName === 'alink' && tag === 'body') ||
1682
- (attrName === 'alt' && tag === 'applet') ||
1683
- (attrName === 'archive' && tag === 'applet') ||
1684
- (attrName === 'background' && tag === 'body') ||
1685
- (attrName === 'bgcolor' && (/^(?:table|t[rdh]|body)$/).test(tag)) ||
1686
- (attrName === 'border' && (/^(?:img|object)$/).test(tag)) ||
1687
- (attrName === 'clear' && tag === 'br') ||
1688
- (attrName === 'code' && tag === 'applet') ||
1689
- (attrName === 'codebase' && tag === 'applet') ||
1690
- (attrName === 'color' && (/^(?:base(?:font)?)$/).test(tag)) ||
1691
- (attrName === 'compact' && (/^(?:dir|[dou]l|menu)$/).test(tag)) ||
1692
- (attrName === 'face' && (/^base(?:font)?$/).test(tag)) ||
1693
- (attrName === 'height' && (/^(?:t[dh]|applet)$/).test(tag)) ||
1694
- (attrName === 'hspace' && (/^(?:applet|img|object)$/).test(tag)) ||
1695
- (attrName === 'language' && tag === 'script') ||
1696
- (attrName === 'link' && tag === 'body') ||
1697
- (attrName === 'name' && tag === 'applet') ||
1698
- (attrName === 'noshade' && tag === 'hr') ||
1699
- (attrName === 'nowrap' && (/^t[dh]$/).test(tag)) ||
1700
- (attrName === 'object' && tag === 'applet') ||
1701
- (attrName === 'prompt' && tag === 'isindex') ||
1702
- (attrName === 'size' && (/^(?:hr|font|basefont)$/).test(tag)) ||
1703
- (attrName === 'start' && tag === 'ol') ||
1704
- (attrName === 'text' && tag === 'body') ||
1705
- (attrName === 'type' && (/^(?:li|ol|ul)$/).test(tag)) ||
1706
- (attrName === 'value' && tag === 'li') ||
1707
- (attrName === 'version' && tag === 'html') ||
1708
- (attrName === 'vlink' && tag === 'body') ||
1709
- (attrName === 'vspace' && (/^(?:applet|img|object)$/).test(tag)) ||
1710
- (attrName === 'width' && (/^(?:hr|td|th|applet|pre)$/).test(tag))
1711
- );
1712
- }
1713
- function isInaccessibleAttribute(attrName, attrValue) {
1714
- return (
1715
- attrName === 'href' &&
1716
- (/^\s*javascript\s*:\s*void\s*(\s+0|\(\s*0\s*\))\s*$/i).test(attrValue)
1717
- );
1718
- }
1719
-
1720
- function Lint() {
1721
- this.log = [ ];
1722
- this._lastElement = null;
1723
- this._isElementRepeated = false;
1724
- }
1725
-
1726
- Lint.prototype.testElement = function(tag) {
1727
- if (isDeprecatedElement(tag)) {
1728
- this.log.push(
1729
- 'Found <span class="deprecated-element">deprecated</span> <strong><code>&lt;' +
1730
- tag + '&gt;</code></strong> element'
1731
- );
1732
- }
1733
- else if (isPresentationalElement(tag)) {
1734
- this.log.push(
1735
- 'Found <span class="presentational-element">presentational</span> <strong><code>&lt;' +
1736
- tag + '&gt;</code></strong> element'
1737
- );
1738
- }
1739
- else {
1740
- this.checkRepeatingElement(tag);
1741
- }
1742
- };
1743
-
1744
- Lint.prototype.checkRepeatingElement = function(tag) {
1745
- if (tag === 'br' && this._lastElement === 'br') {
1746
- this._isElementRepeated = true;
1747
- }
1748
- else if (this._isElementRepeated) {
1749
- this._reportRepeatingElement();
1750
- this._isElementRepeated = false;
1751
- }
1752
- this._lastElement = tag;
1753
- };
1754
-
1755
- Lint.prototype._reportRepeatingElement = function() {
1756
- this.log.push('Found <code>&lt;br></code> sequence. Try replacing it with styling.');
1757
- };
1758
-
1759
- Lint.prototype.testAttribute = function(tag, attrName, attrValue) {
1760
- if (isEventAttribute(attrName)) {
1761
- this.log.push(
1762
- 'Found <span class="event-attribute">event attribute</span> (<strong>' +
1763
- attrName + '</strong>) on <strong><code>&lt;' + tag + '&gt;</code></strong> element.'
1764
- );
1765
- }
1766
- else if (isDeprecatedAttribute(tag, attrName)) {
1767
- this.log.push(
1768
- 'Found <span class="deprecated-attribute">deprecated</span> <strong>' +
1769
- attrName + '</strong> attribute on <strong><code>&lt;' + tag + '&gt;</code></strong> element.'
1770
- );
1771
- }
1772
- else if (isStyleAttribute(attrName)) {
1773
- this.log.push(
1774
- 'Found <span class="style-attribute">style attribute</span> on <strong><code>&lt;' +
1775
- tag + '&gt;</code></strong> element.'
1776
- );
1777
- }
1778
- else if (isInaccessibleAttribute(attrName, attrValue)) {
1779
- this.log.push(
1780
- 'Found <span class="inaccessible-attribute">inaccessible attribute</span> ' +
1781
- '(on <strong><code>&lt;' + tag + '&gt;</code></strong> element).'
1782
- );
1783
- }
1784
- };
1785
-
1786
- Lint.prototype.testChars = function(chars) {
1787
- this._lastElement = '';
1788
- if (/(&nbsp;\s*){2,}/.test(chars)) {
1789
- this.log.push('Found repeating <strong><code>&amp;nbsp;</code></strong> sequence. Try replacing it with styling.');
1790
- }
1791
- };
1792
-
1793
- Lint.prototype.test = function(tag, attrName, attrValue) {
1794
- this.testElement(tag);
1795
- this.testAttribute(tag, attrName, attrValue);
1796
- };
1797
-
1798
- Lint.prototype.populate = function(writeToElement) {
1799
- if (this._isElementRepeated) {
1800
- this._reportRepeatingElement();
1801
- }
1802
-
1803
- if (this.log.length) {
1804
- if (writeToElement) {
1805
- writeToElement.innerHTML = '<ol><li>' + this.log.join('<li>') + '</ol>';
1806
- }
1807
- else {
1808
- var output = ' - ' +
1809
- this.log.join('\n - ')
1810
- .replace(/(<([^>]+)>)/ig, '')
1811
- .replace(/&lt;/g, '<')
1812
- .replace(/&gt;/g, '>');
1813
-
1814
- console.log(output);
1815
- }
1816
- }
1817
- };
1818
-
1819
- global.HTMLLint = Lint;
1820
-
1821
- })(typeof exports === 'undefined' ? this : exports);