html-minifier 0.6.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,560 @@
1
+ /*!
2
+ * HTML Parser By John Resig (ejohn.org)
3
+ * Modified by Juriy "kangax" Zaytsev
4
+ * Original code by Erik Arvidsson, Mozilla Public License
5
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
6
+ */
7
+
8
+ /*
9
+ * // Use like so:
10
+ * HTMLParser(htmlString, {
11
+ * start: function(tag, attrs, unary) {},
12
+ * end: function(tag) {},
13
+ * chars: function(text) {},
14
+ * comment: function(text) {}
15
+ * });
16
+ *
17
+ * // or to get an XML string:
18
+ * HTMLtoXML(htmlString);
19
+ *
20
+ * // or to get an XML DOM Document
21
+ * HTMLtoDOM(htmlString);
22
+ *
23
+ * // or to inject into an existing document/DOM node
24
+ * HTMLtoDOM(htmlString, document);
25
+ * HTMLtoDOM(htmlString, document.body);
26
+ *
27
+ */
28
+
29
+ /* global ActiveXObject, DOMDocument */
30
+
31
+ (function(global) {
32
+ 'use strict';
33
+
34
+ // Regular Expressions for parsing tags and attributes
35
+ var singleAttrIdentifier = /([\w:\.-]+)/,
36
+ singleAttrAssign = /=/,
37
+ singleAttrAssigns = [ singleAttrAssign ],
38
+ singleAttrValues = [
39
+ /"((?:\\.|[^"])*)"/.source, // attr value double quotes
40
+ /'((?:\\.|[^'])*)'/.source, // attr value, single quotes
41
+ /([^>\s]+)/.source // attr value, no quotes
42
+ ],
43
+ startTagOpen = /^<([\w:-]+)/,
44
+ startTagClose = /\s*(\/?)>/,
45
+ endTag = /^<\/([\w:-]+)[^>]*>/,
46
+ endingSlash = /\/>$/,
47
+ doctype = /^<!DOCTYPE [^>]+>/i;
48
+
49
+ var IS_REGEX_CAPTURING_BROKEN = false;
50
+ 'x'.replace(/x(.)?/g, function(m, g) {
51
+ IS_REGEX_CAPTURING_BROKEN = g === '';
52
+ });
53
+
54
+ // Empty Elements - HTML 4.01
55
+ var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr');
56
+
57
+ // Block Elements - HTML 4.01
58
+ // var block = makeMap('address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul');
59
+
60
+ // Inline Elements - HTML 4.01
61
+ 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');
62
+
63
+ // Elements that you can, intentionally, leave open
64
+ // (and which close themselves)
65
+ var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source');
66
+
67
+ // Attributes that have their values filled in disabled='disabled'
68
+ var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
69
+
70
+ // Special Elements (can contain anything)
71
+ var special = makeMap('script,style,noscript');
72
+
73
+ var reCache = {}, stackedTag, reStackedTag, tagMatch;
74
+
75
+ function startTagForHandler( handler ) {
76
+ var customStartTagAttrs;
77
+
78
+ var startTagAttrs = new RegExp(
79
+ '(?:\\s*[\\w:\\.-]+'
80
+ + '(?:\\s*'
81
+ + '(?:' + joinSingleAttrAssigns(handler) + ')'
82
+ + '\\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\\s]+)'
83
+ + ')?'
84
+ + ')*'
85
+ );
86
+
87
+ if ( handler.customAttrSurround ) {
88
+ var attrClauses = [];
89
+
90
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
91
+ // Capture the custom attribute opening and closing markup surrounding the standard attribute rules
92
+ attrClauses[i] = '(?:\\s*'
93
+ + handler.customAttrSurround[i][0].source
94
+ + startTagAttrs.source
95
+ + handler.customAttrSurround[i][1].source
96
+ + ')';
97
+ }
98
+ attrClauses.unshift(startTagAttrs.source);
99
+
100
+ customStartTagAttrs = new RegExp(
101
+ '((?:' + attrClauses.join('|') + ')*)'
102
+ );
103
+ }
104
+ else {
105
+ // No custom attribute wrappers specified, so just capture the standard attribute rules
106
+ customStartTagAttrs = new RegExp('(' + startTagAttrs.source + ')');
107
+ }
108
+
109
+ return new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source);
110
+ }
111
+
112
+ function attrForHandler( handler ) {
113
+ var singleAttr = new RegExp(
114
+ singleAttrIdentifier.source
115
+ + '(?:\\s*'
116
+ + '(' + joinSingleAttrAssigns( handler ) + ')'
117
+ + '\\s*'
118
+ + '(?:'
119
+ + singleAttrValues.join('|')
120
+ + ')'
121
+ + ')?'
122
+ );
123
+
124
+ if ( handler.customAttrSurround ) {
125
+ var attrClauses = [];
126
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
127
+ attrClauses[i] = '(?:'
128
+ + '(' + handler.customAttrSurround[i][0].source + ')'
129
+ + singleAttr.source
130
+ + '(' + handler.customAttrSurround[i][1].source + ')'
131
+ + ')';
132
+ }
133
+ attrClauses.unshift('(?:' + singleAttr.source + ')');
134
+
135
+ return new RegExp(attrClauses.join('|'), 'g');
136
+ }
137
+ else {
138
+ return new RegExp(singleAttr.source, 'g');
139
+ }
140
+ }
141
+
142
+ function joinSingleAttrAssigns( handler ) {
143
+ return singleAttrAssigns.concat(
144
+ handler.customAttrAssign || []
145
+ ).map(function (assign) {
146
+ return '(?:' + assign.source + ')';
147
+ }).join('|');
148
+ }
149
+
150
+ var HTMLParser = global.HTMLParser = function( html, handler ) {
151
+ var index, chars, match, stack = [], last = html, prevTag, nextTag;
152
+ stack.last = function() {
153
+ var last = this[ this.length - 1 ];
154
+ return last && last.tag;
155
+ };
156
+
157
+ var startTag = startTagForHandler(handler);
158
+ var attr = attrForHandler(handler);
159
+
160
+ while ( html ) {
161
+ chars = true;
162
+
163
+ // Make sure we're not in a script or style element
164
+ if ( !stack.last() || !special[ stack.last() ] ) {
165
+
166
+ // Comment:
167
+ if ( /^<!--/.test( html ) ) {
168
+ index = html.indexOf('-->');
169
+
170
+ if ( index >= 0 ) {
171
+ if ( handler.comment ) {
172
+ handler.comment( html.substring( 4, index ) );
173
+ }
174
+ html = html.substring( index + 3 );
175
+ chars = false;
176
+ }
177
+ }
178
+
179
+ // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
180
+ if ( /^<!\[/.test( html ) ) {
181
+ index = html.indexOf(']>');
182
+
183
+ if (index >= 0) {
184
+ if ( handler.comment ) {
185
+ handler.comment( html.substring(2, index + 1 ), true /* non-standard */ );
186
+ }
187
+ html = html.substring( index + 2 );
188
+ chars = false;
189
+ }
190
+ }
191
+
192
+ // Ignored elements?
193
+ else if ( /^<\?/.test( html ) ) {
194
+ index = html.indexOf( '?>', 2 );
195
+ if ( index >= 0 ) {
196
+ if ( handler.chars ) {
197
+ handler.chars( html.substring( 0, index + 2 ) );
198
+ }
199
+ html = html.substring( index + 2 );
200
+ }
201
+ }
202
+
203
+ else if ( /^<%/.test( html ) ) {
204
+ index = html.indexOf( '%>', 2 );
205
+ if ( index >= 0 ) {
206
+ if ( handler.chars ) {
207
+ handler.chars(html.substring( 0, index + 2) );
208
+ }
209
+ html = html.substring( index + 2 );
210
+ }
211
+ }
212
+
213
+ // Doctype:
214
+ else if ( (match = doctype.exec( html )) ) {
215
+ if ( handler.doctype ) {
216
+ handler.doctype( match[0] );
217
+ }
218
+ html = html.substring( match[0].length );
219
+ chars = false;
220
+ }
221
+
222
+ // End tag:
223
+ else if ( /^<\//.test( html ) ) {
224
+ match = html.match( endTag );
225
+
226
+ if ( match ) {
227
+ html = html.substring( match[0].length );
228
+ match[0].replace( endTag, parseEndTag );
229
+ prevTag = '/' + match[1].toLowerCase();
230
+ chars = false;
231
+ }
232
+
233
+ }
234
+ // Start tag:
235
+ else if ( /^</.test( html ) ) {
236
+ match = html.match( startTag );
237
+ if ( match ) {
238
+ html = html.substring( match[0].length );
239
+ match[0].replace( startTag, parseStartTag );
240
+ prevTag = match[1].toLowerCase();
241
+ chars = false;
242
+ }
243
+ }
244
+
245
+ if ( chars ) {
246
+ index = html.indexOf('<');
247
+
248
+ var text = index < 0 ? html : html.substring( 0, index );
249
+ html = index < 0 ? '' : html.substring( index );
250
+
251
+ // next tag
252
+ tagMatch = html.match( startTag );
253
+ if (tagMatch) {
254
+ nextTag = tagMatch[1];
255
+ }
256
+ else {
257
+ tagMatch = html.match( endTag );
258
+ if (tagMatch) {
259
+ nextTag = '/' + tagMatch[1];
260
+ }
261
+ else {
262
+ nextTag = '';
263
+ }
264
+ }
265
+
266
+ if ( handler.chars ) {
267
+ handler.chars(text, prevTag, nextTag);
268
+ }
269
+
270
+ }
271
+
272
+ }
273
+ else {
274
+
275
+ stackedTag = stack.last().toLowerCase();
276
+ reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)<\/' + stackedTag + '[^>]*>', 'i'));
277
+
278
+ html = html.replace(reStackedTag, function(all, text) {
279
+ if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
280
+ text = text
281
+ .replace(/<!--([\s\S]*?)-->/g, '$1')
282
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
283
+ }
284
+
285
+ if ( handler.chars ) {
286
+ handler.chars( text );
287
+ }
288
+
289
+ return '';
290
+ });
291
+
292
+ parseEndTag( '', stackedTag );
293
+ }
294
+
295
+ if ( html === last ) {
296
+ throw 'Parse Error: ' + html;
297
+ }
298
+ last = html;
299
+ }
300
+
301
+ // Clean up any remaining tags
302
+ parseEndTag();
303
+
304
+ function parseStartTag( tag, tagName, rest, unary ) {
305
+ var unarySlash = false;
306
+
307
+ while ( !handler.html5 && stack.last() && inline[ stack.last() ]) {
308
+ parseEndTag( '', stack.last() );
309
+ }
310
+
311
+ if ( closeSelf[ tagName ] && stack.last() === tagName ) {
312
+ parseEndTag( '', tagName );
313
+ }
314
+
315
+ unary = empty[ tagName ] || !!unary;
316
+
317
+ var attrs = [];
318
+
319
+ rest.replace(attr, function () {
320
+ var name, value, fallbackValue, customOpen, customClose, customAssign, quote;
321
+ var ncp = 7; // number of captured parts, scalar
322
+
323
+ // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
324
+ if (IS_REGEX_CAPTURING_BROKEN && arguments[0].indexOf('""') === -1) {
325
+ if (arguments[3] === '') { arguments[3] = undefined; }
326
+ if (arguments[4] === '') { arguments[4] = undefined; }
327
+ if (arguments[5] === '') { arguments[5] = undefined; }
328
+ }
329
+
330
+ name = arguments[1];
331
+ if ( name ) {
332
+ customAssign = arguments[2];
333
+ fallbackValue = arguments[3];
334
+ value = fallbackValue || arguments[4] || arguments[5];
335
+
336
+ if (customAssign) {
337
+ quote = arguments[0].charAt(name.length + customAssign.length);
338
+ quote = (quote === '\'' || quote === '"') ? quote : '';
339
+ }
340
+
341
+ }
342
+ else if ( handler.customAttrSurround ) {
343
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
344
+ name = arguments[i * ncp + 7];
345
+ customAssign = arguments[i * ncp + 8];
346
+ if ( name ) {
347
+ fallbackValue = arguments[i * ncp + 9];
348
+ value = fallbackValue
349
+ || arguments[i * ncp + 10]
350
+ || arguments[i * ncp + 11];
351
+ customOpen = arguments[i * ncp + 6];
352
+ customClose = arguments[i * ncp + 12];
353
+ break;
354
+ }
355
+ }
356
+ }
357
+
358
+ if ( value === undefined ) {
359
+ value = fillAttrs[name] ? name : fallbackValue;
360
+ }
361
+
362
+ attrs.push({
363
+ name: name,
364
+ value: value,
365
+ escaped: value && value.replace(/(^|.)("+)/g, function(match) {
366
+ return match.replace(/"/g, '&quot;');
367
+ }),
368
+ customAssign: customAssign || '=',
369
+ customOpen: customOpen || '',
370
+ customClose: customClose || '',
371
+ quote: quote || ''
372
+ });
373
+ });
374
+
375
+ if ( !unary ) {
376
+ stack.push( { tag: tagName, attrs: attrs } );
377
+ }
378
+ else {
379
+ unarySlash = tag.match( endingSlash );
380
+ }
381
+
382
+
383
+ if ( handler.start ) {
384
+ handler.start( tagName, attrs, unary, unarySlash );
385
+ }
386
+ }
387
+
388
+ function parseEndTag( tag, tagName ) {
389
+ var pos;
390
+
391
+ // If no tag name is provided, clean shop
392
+ if ( !tagName ) {
393
+ pos = 0;
394
+ }
395
+ else {
396
+ // Find the closest opened tag of the same type
397
+ var needle = tagName.toLowerCase();
398
+ for ( pos = stack.length - 1; pos >= 0; pos-- ) {
399
+ if ( stack[ pos ].tag.toLowerCase() === needle ) {
400
+ break;
401
+ }
402
+ }
403
+ }
404
+
405
+ if ( pos >= 0 ) {
406
+ // Close all the open elements, up the stack
407
+ for ( var i = stack.length - 1; i >= pos; i-- ) {
408
+ if ( handler.end ) {
409
+ handler.end( stack[ i ].tag, stack[ i ].attrs );
410
+ }
411
+ }
412
+
413
+ // Remove the open elements from the stack
414
+ stack.length = pos;
415
+ }
416
+ }
417
+ };
418
+
419
+ global.HTMLtoXML = function( html ) {
420
+ var results = '';
421
+
422
+ new HTMLParser(html, {
423
+ start: function( tag, attrs, unary ) {
424
+ results += '<' + tag;
425
+
426
+ for ( var i = 0; i < attrs.length; i++ ) {
427
+ results += ' ' + attrs[i].name + '="' + attrs[i].escaped + '"';
428
+ }
429
+
430
+ results += (unary ? '/' : '') + '>';
431
+ },
432
+ end: function( tag ) {
433
+ results += '</' + tag + '>';
434
+ },
435
+ chars: function( text ) {
436
+ results += text;
437
+ },
438
+ comment: function( text ) {
439
+ results += '<!--' + text + '-->';
440
+ },
441
+ ignore: function(text) {
442
+ results += text;
443
+ }
444
+ });
445
+
446
+ return results;
447
+ };
448
+
449
+ global.HTMLtoDOM = function( html, doc ) {
450
+ // There can be only one of these elements
451
+ var one = makeMap('html,head,body,title');
452
+
453
+ // Enforce a structure for the document
454
+ var structure = {
455
+ link: 'head',
456
+ base: 'head'
457
+ };
458
+
459
+ if ( !doc ) {
460
+ if ( typeof DOMDocument !== 'undefined' ) {
461
+ doc = new DOMDocument();
462
+ }
463
+ else if ( typeof document !== 'undefined' && document.implementation && document.implementation.createDocument ) {
464
+ doc = document.implementation.createDocument('', '', null);
465
+ }
466
+ else if ( typeof ActiveX !== 'undefined' ) {
467
+ doc = new ActiveXObject('Msxml.DOMDocument');
468
+ }
469
+
470
+ }
471
+ else {
472
+ doc = doc.ownerDocument ||
473
+ doc.getOwnerDocument && doc.getOwnerDocument() ||
474
+ doc;
475
+ }
476
+
477
+ var elems = [],
478
+ documentElement = doc.documentElement ||
479
+ doc.getDocumentElement && doc.getDocumentElement();
480
+
481
+ // If we're dealing with an empty document then we
482
+ // need to pre-populate it with the HTML document structure
483
+ if ( !documentElement && doc.createElement ) {
484
+ (function() {
485
+ var html = doc.createElement('html');
486
+ var head = doc.createElement('head');
487
+ head.appendChild( doc.createElement('title') );
488
+ html.appendChild( head );
489
+ html.appendChild( doc.createElement('body') );
490
+ doc.appendChild( html );
491
+ })();
492
+ }
493
+
494
+ // Find all the unique elements
495
+ if ( doc.getElementsByTagName ) {
496
+ for ( var i in one ) {
497
+ one[ i ] = doc.getElementsByTagName( i )[0];
498
+ }
499
+ }
500
+
501
+ // If we're working with a document, inject contents into
502
+ // the body element
503
+ var curParentNode = one.body;
504
+
505
+ new HTMLParser( html, {
506
+ start: function( tagName, attrs, unary ) {
507
+ // If it's a pre-built element, then we can ignore
508
+ // its construction
509
+ if ( one[ tagName ] ) {
510
+ curParentNode = one[ tagName ];
511
+ return;
512
+ }
513
+
514
+ var elem = doc.createElement( tagName );
515
+
516
+ for ( var attr in attrs ) {
517
+ elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
518
+ }
519
+
520
+ if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] !== 'boolean' ) {
521
+ one[ structure[ tagName ] ].appendChild( elem );
522
+ }
523
+ else if ( curParentNode && curParentNode.appendChild ) {
524
+ curParentNode.appendChild( elem );
525
+ }
526
+
527
+ if ( !unary ) {
528
+ elems.push( elem );
529
+ curParentNode = elem;
530
+ }
531
+ },
532
+ end: function( /* tag */ ) {
533
+ elems.length -= 1;
534
+
535
+ // Init the new parentNode
536
+ curParentNode = elems[ elems.length - 1 ];
537
+ },
538
+ chars: function( text ) {
539
+ curParentNode.appendChild( doc.createTextNode( text ) );
540
+ },
541
+ comment: function( /*text*/ ) {
542
+ // create comment node
543
+ },
544
+ ignore: function( /* text */ ) {
545
+ // What to do here?
546
+ }
547
+ });
548
+
549
+ return doc;
550
+ };
551
+
552
+ function makeMap(str) {
553
+ var obj = {}, items = str.split(',');
554
+ for ( var i = 0; i < items.length; i++ ) {
555
+ obj[ items[i] ] = true;
556
+ obj[ items[i].toUpperCase() ] = true;
557
+ }
558
+ return obj;
559
+ }
560
+ })(typeof exports === 'undefined' ? this : exports);