html-minifier 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -52
- package/cli.js +220 -290
- package/package.json +12 -14
- package/sample-cli-config-file.conf +29 -16
- package/src/.eslintrc.json +5 -0
- package/src/htmllint.js +153 -144
- package/src/htmlminifier.js +1029 -925
- package/src/htmlparser.js +405 -409
- package/src/tokenchain.js +58 -0
- package/src/utils.js +18 -0
- package/CHANGELOG.md +0 -120
- package/dist/htmlminifier.js +0 -1821
- package/dist/htmlminifier.min.js +0 -6
package/dist/htmlminifier.js
DELETED
|
@@ -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, '"') + '"';
|
|
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, '"');
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
attrValue = attrValue.replace(/'/g, ''');
|
|
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><' +
|
|
1730
|
-
tag + '></code></strong> element'
|
|
1731
|
-
);
|
|
1732
|
-
}
|
|
1733
|
-
else if (isPresentationalElement(tag)) {
|
|
1734
|
-
this.log.push(
|
|
1735
|
-
'Found <span class="presentational-element">presentational</span> <strong><code><' +
|
|
1736
|
-
tag + '></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><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><' + tag + '></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><' + tag + '></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><' +
|
|
1775
|
-
tag + '></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><' + tag + '></code></strong> element).'
|
|
1782
|
-
);
|
|
1783
|
-
}
|
|
1784
|
-
};
|
|
1785
|
-
|
|
1786
|
-
Lint.prototype.testChars = function(chars) {
|
|
1787
|
-
this._lastElement = '';
|
|
1788
|
-
if (/( \s*){2,}/.test(chars)) {
|
|
1789
|
-
this.log.push('Found repeating <strong><code>&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(/</g, '<')
|
|
1812
|
-
.replace(/>/g, '>');
|
|
1813
|
-
|
|
1814
|
-
console.log(output);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
};
|
|
1818
|
-
|
|
1819
|
-
global.HTMLLint = Lint;
|
|
1820
|
-
|
|
1821
|
-
})(typeof exports === 'undefined' ? this : exports);
|