html-minifier 1.1.1 → 1.4.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/src/htmlparser.js CHANGED
@@ -28,533 +28,500 @@
28
28
 
29
29
  /* global ActiveXObject, DOMDocument */
30
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');
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 + ')');
31
+ 'use strict';
32
+
33
+ var createMapFromString = require('./utils').createMapFromString;
34
+
35
+ function makeMap(values) {
36
+ return createMapFromString(values, true);
37
+ }
38
+
39
+ // Regular Expressions for parsing tags and attributes
40
+ var singleAttrIdentifier = /([^\s"'<>\/=]+)/,
41
+ singleAttrAssign = /=/,
42
+ singleAttrAssigns = [ singleAttrAssign ],
43
+ singleAttrValues = [
44
+ // attr value double quotes
45
+ /"([^"]*)"+/.source,
46
+ // attr value, single quotes
47
+ /'([^']*)'+/.source,
48
+ // attr value, no quotes
49
+ /([^\s"'=<>`]+)/.source
50
+ ],
51
+ // https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
52
+ qnameCapture = (function() {
53
+ var ncname = require('ncname').source.slice(1, -1);
54
+ return '((?:' + ncname + '\\:)?' + ncname + ')';
55
+ })(),
56
+ startTagOpen = new RegExp('^<' + qnameCapture),
57
+ startTagClose = /^\s*(\/?)>/,
58
+ endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>'),
59
+ doctype = /^<!DOCTYPE [^>]+>/i;
60
+
61
+ var IS_REGEX_CAPTURING_BROKEN = false;
62
+ 'x'.replace(/x(.)?/g, function(m, g) {
63
+ IS_REGEX_CAPTURING_BROKEN = g === '';
64
+ });
65
+
66
+ // Empty Elements
67
+ var empty = makeMap('area,base,basefont,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr');
68
+
69
+ // Inline Elements
70
+ 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');
71
+
72
+ // Elements that you can, intentionally, leave open
73
+ // (and which close themselves)
74
+ var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source');
75
+
76
+ // Attributes that have their values filled in disabled='disabled'
77
+ var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
78
+
79
+ // Special Elements (can contain anything)
80
+ var special = makeMap('script,style');
81
+
82
+ // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
83
+ // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
84
+ var nonPhrasing = makeMap('address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track');
85
+
86
+ var reCache = {};
87
+
88
+ function attrForHandler(handler) {
89
+ var pattern = singleAttrIdentifier.source
90
+ + '(?:\\s*(' + joinSingleAttrAssigns(handler) + ')'
91
+ + '\\s*(?:' + singleAttrValues.join('|') + '))?';
92
+ if (handler.customAttrSurround) {
93
+ var attrClauses = [];
94
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
95
+ attrClauses[i] = '(?:'
96
+ + '(' + handler.customAttrSurround[i][0].source + ')\\s*'
97
+ + pattern
98
+ + '\\s*(' + handler.customAttrSurround[i][1].source + ')'
99
+ + ')';
107
100
  }
108
-
109
- return new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source);
101
+ attrClauses.push('(?:' + pattern + ')');
102
+ pattern = '(?:' + attrClauses.join('|') + ')';
110
103
  }
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
-
104
+ return new RegExp('^\\s*' + pattern);
105
+ }
106
+
107
+ function joinSingleAttrAssigns( handler ) {
108
+ return singleAttrAssigns.concat(
109
+ handler.customAttrAssign || []
110
+ ).map(function (assign) {
111
+ return '(?:' + assign.source + ')';
112
+ }).join('|');
113
+ }
114
+
115
+ function HTMLParser( html, handler ) {
116
+ var stack = [], lastTag;
117
+ var attribute = attrForHandler(handler);
118
+ var last, prevTag, nextTag;
119
+ while ( html ) {
120
+ last = html;
121
+ // Make sure we're not in a script or style element
122
+ if ( !lastTag || !special(lastTag) ) {
123
+ var textEnd = html.indexOf('<');
124
+ if (textEnd === 0) {
166
125
  // Comment:
167
126
  if ( /^<!--/.test( html ) ) {
168
- index = html.indexOf('-->');
127
+ var commentEnd = html.indexOf('-->');
169
128
 
170
- if ( index >= 0 ) {
129
+ if ( commentEnd >= 0 ) {
171
130
  if ( handler.comment ) {
172
- handler.comment( html.substring( 4, index ) );
131
+ handler.comment( html.substring( 4, commentEnd ) );
173
132
  }
174
- html = html.substring( index + 3 );
175
- chars = false;
133
+ html = html.substring( commentEnd + 3 );
134
+ prevTag = '';
135
+ continue;
176
136
  }
177
137
  }
178
138
 
179
139
  // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
180
140
  if ( /^<!\[/.test( html ) ) {
181
- index = html.indexOf(']>');
141
+ var conditionalEnd = html.indexOf(']>');
182
142
 
183
- if (index >= 0) {
143
+ if (conditionalEnd >= 0) {
184
144
  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) );
145
+ handler.comment( html.substring(2, conditionalEnd + 1 ), true /* non-standard */ );
208
146
  }
209
- html = html.substring( index + 2 );
147
+ html = html.substring( conditionalEnd + 2 );
148
+ prevTag = '';
149
+ continue;
210
150
  }
211
151
  }
212
152
 
213
153
  // Doctype:
214
- else if ( (match = doctype.exec( html )) ) {
154
+ var doctypeMatch = html.match( doctype );
155
+ if ( doctypeMatch ) {
215
156
  if ( handler.doctype ) {
216
- handler.doctype( match[0] );
157
+ handler.doctype( doctypeMatch[0] );
217
158
  }
218
- html = html.substring( match[0].length );
219
- chars = false;
159
+ html = html.substring( doctypeMatch[0].length );
160
+ prevTag = '';
161
+ continue;
220
162
  }
221
163
 
222
164
  // 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
-
165
+ var endTagMatch = html.match( endTag );
166
+ if ( endTagMatch ) {
167
+ html = html.substring( endTagMatch[0].length );
168
+ endTagMatch[0].replace( endTag, parseEndTag );
169
+ prevTag = '/' + endTagMatch[1].toLowerCase();
170
+ continue;
233
171
  }
172
+
234
173
  // 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
- }
174
+ var startTagMatch = parseStartTag(html);
175
+ if ( startTagMatch ) {
176
+ html = startTagMatch.rest;
177
+ handleStartTag(startTagMatch);
178
+ prevTag = startTagMatch.tagName.toLowerCase();
179
+ continue;
243
180
  }
181
+ }
244
182
 
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
- }
183
+ var text;
184
+ if (textEnd >= 0) {
185
+ text = html.substring( 0, textEnd );
186
+ html = html.substring( textEnd );
187
+ }
188
+ else {
189
+ text = html;
190
+ html = '';
191
+ }
271
192
 
193
+ // next tag
194
+ var nextTagMatch = parseStartTag(html);
195
+ if (nextTagMatch) {
196
+ nextTag = nextTagMatch.tagName;
272
197
  }
273
198
  else {
199
+ nextTagMatch = html.match( endTag );
200
+ if (nextTagMatch) {
201
+ nextTag = '/' + nextTagMatch[1];
202
+ }
203
+ else {
204
+ nextTag = '';
205
+ }
206
+ }
274
207
 
275
- stackedTag = stack.last().toLowerCase();
276
- reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)<\/' + stackedTag + '[^>]*>', 'i'));
208
+ if ( handler.chars ) {
209
+ handler.chars(text, prevTag, nextTag);
210
+ }
211
+ prevTag = '';
277
212
 
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
- }
213
+ }
214
+ else {
215
+ var stackedTag = lastTag.toLowerCase();
216
+ var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)<\/' + stackedTag + '[^>]*>', 'i'));
217
+
218
+ html = html.replace(reStackedTag, function(all, text) {
219
+ if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
220
+ text = text
221
+ .replace(/<!--([\s\S]*?)-->/g, '$1')
222
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
223
+ }
284
224
 
285
- if ( handler.chars ) {
286
- handler.chars( text );
287
- }
225
+ if ( handler.chars ) {
226
+ handler.chars( text );
227
+ }
288
228
 
289
- return '';
290
- });
229
+ return '';
230
+ });
291
231
 
292
- parseEndTag( '', stackedTag );
293
- }
232
+ parseEndTag( '</' + stackedTag + '>', stackedTag );
233
+ }
294
234
 
295
- if ( html === last ) {
296
- throw 'Parse Error: ' + html;
297
- }
298
- last = html;
235
+ if ( html === last ) {
236
+ throw new Error('Parse Error: ' + html);
299
237
  }
238
+ }
300
239
 
240
+ if (!handler.partialMarkup) {
301
241
  // Clean up any remaining tags
302
242
  parseEndTag();
243
+ }
303
244
 
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() );
245
+ function parseStartTag(input) {
246
+ var start = input.match(startTagOpen);
247
+ if (start) {
248
+ var match = {
249
+ tagName: start[1],
250
+ attrs: []
251
+ };
252
+ input = input.slice(start[0].length);
253
+ var end, attr;
254
+ while (!(end = input.match(startTagClose)) && (attr = input.match(attribute))) {
255
+ input = input.slice(attr[0].length);
256
+ match.attrs.push(attr);
309
257
  }
310
-
311
- if ( closeSelf[ tagName ] && stack.last() === tagName ) {
312
- parseEndTag( '', tagName );
258
+ if (end) {
259
+ match.unarySlash = end[1];
260
+ match.rest = input.slice(end[0].length);
261
+ return match;
313
262
  }
263
+ }
264
+ }
314
265
 
315
- unary = empty[ tagName ] || !!unary;
266
+ function handleStartTag(match) {
267
+ var tagName = match.tagName;
268
+ var unarySlash = match.unarySlash;
316
269
 
317
- var attrs = [];
270
+ if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
271
+ parseEndTag( '', lastTag );
272
+ }
318
273
 
319
- rest.replace(attr, function () {
320
- var name, value, fallbackValue, customOpen, customClose, customAssign, quote;
321
- var ncp = 7; // number of captured parts, scalar
274
+ if (!handler.html5) {
275
+ while (lastTag && inline(lastTag)) {
276
+ parseEndTag( '', lastTag );
277
+ }
278
+ }
322
279
 
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
- }
280
+ if ( closeSelf(tagName) && lastTag === tagName ) {
281
+ parseEndTag( '', tagName );
282
+ }
329
283
 
330
- name = arguments[1];
331
- if ( name ) {
332
- customAssign = arguments[2];
333
- fallbackValue = arguments[3];
334
- value = fallbackValue || arguments[4] || arguments[5];
284
+ var unary = empty(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
335
285
 
336
- if (customAssign) {
337
- quote = arguments[0].charAt(name.length + customAssign.length);
338
- quote = (quote === '\'' || quote === '"') ? quote : '';
339
- }
286
+ var attrs = match.attrs.map(function(args) {
287
+ var name, value, fallbackValue, customOpen, customClose, customAssign, quote;
288
+ var ncp = 7; // number of captured parts, scalar
340
289
 
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
- }
290
+ // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
291
+ if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
292
+ if (args[3] === '') { args[3] = undefined; }
293
+ if (args[4] === '') { args[4] = undefined; }
294
+ if (args[5] === '') { args[5] = undefined; }
295
+ }
357
296
 
358
- if ( value === undefined ) {
359
- value = fillAttrs[name] ? name : fallbackValue;
297
+ var j = 1;
298
+ if (handler.customAttrSurround) {
299
+ for (var i = 0, l = handler.customAttrSurround.length; i < l; i++, j += ncp) {
300
+ name = args[j + 1];
301
+ customAssign = args[j + 2];
302
+ if (name) {
303
+ fallbackValue = args[j + 3];
304
+ value = fallbackValue || args[j + 4] || args[j + 5];
305
+ quote = fallbackValue ? '"' : value ? '\'' : '';
306
+ customOpen = args[j];
307
+ customClose = args[j + 6];
308
+ break;
309
+ }
360
310
  }
311
+ }
361
312
 
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 } );
313
+ if (!name && (name = args[j])) {
314
+ customAssign = args[j + 1];
315
+ fallbackValue = args[j + 2];
316
+ value = fallbackValue || args[j + 3] || args[j + 4];
317
+ quote = fallbackValue ? '"' : value ? '\'' : '';
377
318
  }
378
- else {
379
- unarySlash = tag.match( endingSlash );
319
+
320
+ if (value === undefined) {
321
+ value = fillAttrs(name) ? name : fallbackValue;
380
322
  }
381
323
 
324
+ return {
325
+ name: name,
326
+ value: value,
327
+ customAssign: customAssign || '=',
328
+ customOpen: customOpen || '',
329
+ customClose: customClose || '',
330
+ quote: quote || ''
331
+ };
332
+ });
382
333
 
383
- if ( handler.start ) {
384
- handler.start( tagName, attrs, unary, unarySlash );
385
- }
334
+ if ( !unary ) {
335
+ stack.push( { tag: tagName, attrs: attrs } );
336
+ lastTag = tagName;
337
+ unarySlash = '';
386
338
  }
387
339
 
388
- function parseEndTag( tag, tagName ) {
389
- var pos;
340
+ if ( handler.start ) {
341
+ handler.start( tagName, attrs, unary, unarySlash );
342
+ }
343
+ }
390
344
 
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
- }
345
+ function parseEndTag( tag, tagName ) {
346
+ var pos;
347
+
348
+ // If no tag name is provided, clean shop
349
+ if ( !tagName ) {
350
+ pos = 0;
351
+ }
352
+ else {
353
+ // Find the closest opened tag of the same type
354
+ var needle = tagName.toLowerCase();
355
+ for ( pos = stack.length - 1; pos >= 0; pos-- ) {
356
+ if ( stack[ pos ].tag.toLowerCase() === needle ) {
357
+ break;
402
358
  }
403
359
  }
360
+ }
404
361
 
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
- }
362
+ if ( pos >= 0 ) {
363
+ // Close all the open elements, up the stack
364
+ for ( var i = stack.length - 1; i >= pos; i-- ) {
365
+ if ( handler.end ) {
366
+ handler.end( stack[ i ].tag, stack[ i ].attrs, i > pos || !tag );
411
367
  }
368
+ }
412
369
 
413
- // Remove the open elements from the stack
414
- stack.length = pos;
370
+ // Remove the open elements from the stack
371
+ stack.length = pos;
372
+ lastTag = pos && stack[ pos - 1 ].tag;
373
+ }
374
+ else if (tagName.toLowerCase() === 'br') {
375
+ if (handler.start) {
376
+ handler.start(tagName, [], true, '');
415
377
  }
416
378
  }
417
- };
418
-
419
- global.HTMLtoXML = function( html ) {
420
- var results = '';
379
+ else if (tagName.toLowerCase() === 'p') {
380
+ if (handler.start) {
381
+ handler.start(tagName, [], false, '', true);
382
+ }
383
+ if (handler.end) {
384
+ handler.end(tagName, []);
385
+ }
386
+ }
387
+ }
388
+ }
421
389
 
422
- new HTMLParser(html, {
423
- start: function( tag, attrs, unary ) {
424
- results += '<' + tag;
390
+ exports.HTMLParser = HTMLParser;
391
+ exports.HTMLtoXML = function( html ) {
392
+ var results = '';
425
393
 
426
- for ( var i = 0; i < attrs.length; i++ ) {
427
- results += ' ' + attrs[i].name + '="' + attrs[i].escaped + '"';
428
- }
394
+ new HTMLParser(html, {
395
+ start: function( tag, attrs, unary ) {
396
+ results += '<' + tag;
429
397
 
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;
398
+ for ( var i = 0; i < attrs.length; i++ ) {
399
+ results += ' ' + attrs[i].name + '="' + (attrs[i].value || '').replace(/"/g, '&#34;') + '"';
443
400
  }
444
- });
445
401
 
446
- return results;
447
- };
402
+ results += (unary ? '/' : '') + '>';
403
+ },
404
+ end: function( tag ) {
405
+ results += '</' + tag + '>';
406
+ },
407
+ chars: function( text ) {
408
+ results += text;
409
+ },
410
+ comment: function( text ) {
411
+ results += '<!--' + text + '-->';
412
+ },
413
+ ignore: function(text) {
414
+ results += text;
415
+ }
416
+ });
448
417
 
449
- global.HTMLtoDOM = function( html, doc ) {
450
- // There can be only one of these elements
451
- var one = makeMap('html,head,body,title');
418
+ return results;
419
+ };
452
420
 
453
- // Enforce a structure for the document
454
- var structure = {
455
- link: 'head',
456
- base: 'head'
457
- };
421
+ exports.HTMLtoDOM = function( html, doc ) {
422
+ // There can be only one of these elements
423
+ var one = {
424
+ html: true,
425
+ head: true,
426
+ body: true,
427
+ title: true
428
+ };
458
429
 
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
- }
430
+ // Enforce a structure for the document
431
+ var structure = {
432
+ link: 'head',
433
+ base: 'head'
434
+ };
469
435
 
436
+ if ( !doc ) {
437
+ if ( typeof DOMDocument !== 'undefined' ) {
438
+ doc = new DOMDocument();
470
439
  }
471
- else {
472
- doc = doc.ownerDocument ||
473
- doc.getOwnerDocument && doc.getOwnerDocument() ||
474
- doc;
440
+ else if ( typeof document !== 'undefined' && document.implementation && document.implementation.createDocument ) {
441
+ doc = document.implementation.createDocument('', '', null);
442
+ }
443
+ else if ( typeof ActiveX !== 'undefined' ) {
444
+ doc = new ActiveXObject('Msxml.DOMDocument');
475
445
  }
476
446
 
477
- var elems = [],
447
+ }
448
+ else {
449
+ doc = doc.ownerDocument ||
450
+ doc.getOwnerDocument && doc.getOwnerDocument() ||
451
+ doc;
452
+ }
453
+
454
+ var elems = [],
478
455
  documentElement = doc.documentElement ||
479
456
  doc.getDocumentElement && doc.getDocumentElement();
480
457
 
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
- }
458
+ // If we're dealing with an empty document then we
459
+ // need to pre-populate it with the HTML document structure
460
+ if ( !documentElement && doc.createElement ) {
461
+ (function() {
462
+ var html = doc.createElement('html');
463
+ var head = doc.createElement('head');
464
+ head.appendChild( doc.createElement('title') );
465
+ html.appendChild( head );
466
+ html.appendChild( doc.createElement('body') );
467
+ doc.appendChild( html );
468
+ })();
469
+ }
493
470
 
494
- // Find all the unique elements
495
- if ( doc.getElementsByTagName ) {
496
- for ( var i in one ) {
497
- one[ i ] = doc.getElementsByTagName( i )[0];
498
- }
471
+ // Find all the unique elements
472
+ if ( doc.getElementsByTagName ) {
473
+ for ( var i in one ) {
474
+ one[ i ] = doc.getElementsByTagName( i )[0];
499
475
  }
476
+ }
500
477
 
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
- }
478
+ // If we're working with a document, inject contents into
479
+ // the body element
480
+ var curParentNode = one.body;
481
+
482
+ new HTMLParser( html, {
483
+ start: function( tagName, attrs, unary ) {
484
+ // If it's a pre-built element, then we can ignore
485
+ // its construction
486
+ if ( one[ tagName ] ) {
487
+ curParentNode = one[ tagName ];
488
+ return;
489
+ }
519
490
 
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
- }
491
+ var elem = doc.createElement( tagName );
526
492
 
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?
493
+ for ( var attr in attrs ) {
494
+ elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
546
495
  }
547
- });
548
496
 
549
- return doc;
550
- };
497
+ if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] !== 'boolean' ) {
498
+ one[ structure[ tagName ] ].appendChild( elem );
499
+ }
500
+ else if ( curParentNode && curParentNode.appendChild ) {
501
+ curParentNode.appendChild( elem );
502
+ }
551
503
 
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;
504
+ if ( !unary ) {
505
+ elems.push( elem );
506
+ curParentNode = elem;
507
+ }
508
+ },
509
+ end: function( /* tag */ ) {
510
+ elems.length -= 1;
511
+
512
+ // Init the new parentNode
513
+ curParentNode = elems[ elems.length - 1 ];
514
+ },
515
+ chars: function( text ) {
516
+ curParentNode.appendChild( doc.createTextNode( text ) );
517
+ },
518
+ comment: function( /* text */ ) {
519
+ // create comment node
520
+ },
521
+ ignore: function( /* text */ ) {
522
+ // What to do here?
557
523
  }
558
- return obj;
559
- }
560
- })(typeof exports === 'undefined' ? this : exports);
524
+ });
525
+
526
+ return doc;
527
+ };