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/src/htmlparser.js
CHANGED
|
@@ -28,506 +28,502 @@
|
|
|
28
28
|
|
|
29
29
|
/* global ActiveXObject, DOMDocument */
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
'((?:' + attrClauses.join('|') + ')*)'
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// No custom attribute wrappers specified, so just capture the standard attribute rules
|
|
105
|
-
customStartTagAttrs = new RegExp('(' + startTagAttrs.source + ')');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function attrForHandler( handler ) {
|
|
112
|
-
var singleAttr = new RegExp(
|
|
113
|
-
singleAttrIdentifier.source
|
|
114
|
-
+ '(?:\\s*'
|
|
115
|
-
+ '(' + joinSingleAttrAssigns( handler ) + ')'
|
|
116
|
-
+ '\\s*'
|
|
117
|
-
+ '(?:'
|
|
118
|
-
+ singleAttrValues.join('|')
|
|
119
|
-
+ ')'
|
|
120
|
-
+ ')?'
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if ( handler.customAttrSurround ) {
|
|
124
|
-
var attrClauses = [];
|
|
125
|
-
for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
|
|
126
|
-
attrClauses[i] = '(?:'
|
|
127
|
-
+ '(' + handler.customAttrSurround[i][0].source + ')\\s*'
|
|
128
|
-
+ singleAttr.source
|
|
129
|
-
+ '\\s*(' + handler.customAttrSurround[i][1].source + ')'
|
|
130
|
-
+ ')';
|
|
131
|
-
}
|
|
132
|
-
attrClauses.unshift('(?:' + singleAttr.source + ')');
|
|
133
|
-
|
|
134
|
-
return new RegExp(attrClauses.join('|'), 'g');
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
return new RegExp(singleAttr.source, 'g');
|
|
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
|
+
')';
|
|
138
100
|
}
|
|
101
|
+
attrClauses.push('(?:' + pattern + ')');
|
|
102
|
+
pattern = '(?:' + attrClauses.join('|') + ')';
|
|
139
103
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if ( !stack.last() || !special[ stack.last() ] ) {
|
|
162
|
-
|
|
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) {
|
|
163
125
|
// Comment:
|
|
164
|
-
if (
|
|
165
|
-
|
|
126
|
+
if (/^<!--/.test(html)) {
|
|
127
|
+
var commentEnd = html.indexOf('-->');
|
|
166
128
|
|
|
167
|
-
if (
|
|
168
|
-
if (
|
|
169
|
-
handler.comment(
|
|
129
|
+
if (commentEnd >= 0) {
|
|
130
|
+
if (handler.comment) {
|
|
131
|
+
handler.comment(html.substring(4, commentEnd));
|
|
170
132
|
}
|
|
171
|
-
html = html.substring(
|
|
133
|
+
html = html.substring(commentEnd + 3);
|
|
172
134
|
prevTag = '';
|
|
173
135
|
continue;
|
|
174
136
|
}
|
|
175
137
|
}
|
|
176
138
|
|
|
177
139
|
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
|
|
178
|
-
if (
|
|
179
|
-
|
|
140
|
+
if (/^<!\[/.test(html)) {
|
|
141
|
+
var conditionalEnd = html.indexOf(']>');
|
|
180
142
|
|
|
181
|
-
if (
|
|
182
|
-
if (
|
|
183
|
-
handler.comment(
|
|
143
|
+
if (conditionalEnd >= 0) {
|
|
144
|
+
if (handler.comment) {
|
|
145
|
+
handler.comment(html.substring(2, conditionalEnd + 1), true /* non-standard */);
|
|
184
146
|
}
|
|
185
|
-
html = html.substring(
|
|
147
|
+
html = html.substring(conditionalEnd + 2);
|
|
186
148
|
prevTag = '';
|
|
187
149
|
continue;
|
|
188
150
|
}
|
|
189
151
|
}
|
|
190
152
|
|
|
191
153
|
// Doctype:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
154
|
+
var doctypeMatch = html.match(doctype);
|
|
155
|
+
if (doctypeMatch) {
|
|
156
|
+
if (handler.doctype) {
|
|
157
|
+
handler.doctype(doctypeMatch[0]);
|
|
195
158
|
}
|
|
196
|
-
html = html.substring(
|
|
159
|
+
html = html.substring(doctypeMatch[0].length);
|
|
197
160
|
prevTag = '';
|
|
198
161
|
continue;
|
|
199
162
|
}
|
|
200
163
|
|
|
201
164
|
// End tag:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
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;
|
|
211
171
|
}
|
|
172
|
+
|
|
212
173
|
// Start tag:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
174
|
+
var startTagMatch = parseStartTag(html);
|
|
175
|
+
if (startTagMatch) {
|
|
176
|
+
html = startTagMatch.rest;
|
|
177
|
+
handleStartTag(startTagMatch);
|
|
178
|
+
prevTag = startTagMatch.tagName.toLowerCase();
|
|
179
|
+
continue;
|
|
221
180
|
}
|
|
181
|
+
}
|
|
222
182
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
html =
|
|
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
|
+
}
|
|
227
192
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
193
|
+
// next tag
|
|
194
|
+
var nextTagMatch = parseStartTag(html);
|
|
195
|
+
if (nextTagMatch) {
|
|
196
|
+
nextTag = nextTagMatch.tagName;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
nextTagMatch = html.match(endTag);
|
|
200
|
+
if (nextTagMatch) {
|
|
201
|
+
nextTag = '/' + nextTagMatch[1];
|
|
232
202
|
}
|
|
233
203
|
else {
|
|
234
|
-
|
|
235
|
-
if (tagMatch) {
|
|
236
|
-
nextTag = '/' + tagMatch[1];
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
nextTag = '';
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if ( handler.chars ) {
|
|
244
|
-
handler.chars(text, prevTag, nextTag);
|
|
204
|
+
nextTag = '';
|
|
245
205
|
}
|
|
246
|
-
prevTag = '';
|
|
247
|
-
|
|
248
206
|
}
|
|
249
|
-
else {
|
|
250
207
|
|
|
251
|
-
|
|
252
|
-
|
|
208
|
+
if (handler.chars) {
|
|
209
|
+
handler.chars(text, prevTag, nextTag);
|
|
210
|
+
}
|
|
211
|
+
prevTag = '';
|
|
253
212
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
}
|
|
260
224
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
225
|
+
if (handler.chars) {
|
|
226
|
+
handler.chars(text);
|
|
227
|
+
}
|
|
264
228
|
|
|
265
|
-
|
|
266
|
-
|
|
229
|
+
return '';
|
|
230
|
+
});
|
|
267
231
|
|
|
268
|
-
|
|
269
|
-
|
|
232
|
+
parseEndTag('</' + stackedTag + '>', stackedTag);
|
|
233
|
+
}
|
|
270
234
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
last = html;
|
|
235
|
+
if (html === last) {
|
|
236
|
+
throw new Error('Parse Error: ' + html);
|
|
275
237
|
}
|
|
238
|
+
}
|
|
276
239
|
|
|
240
|
+
if (!handler.partialMarkup) {
|
|
277
241
|
// Clean up any remaining tags
|
|
278
242
|
parseEndTag();
|
|
243
|
+
}
|
|
279
244
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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);
|
|
285
257
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
258
|
+
if (end) {
|
|
259
|
+
match.unarySlash = end[1];
|
|
260
|
+
match.rest = input.slice(end[0].length);
|
|
261
|
+
return match;
|
|
289
262
|
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
290
265
|
|
|
291
|
-
|
|
266
|
+
function handleStartTag(match) {
|
|
267
|
+
var tagName = match.tagName;
|
|
268
|
+
var unarySlash = match.unarySlash;
|
|
292
269
|
|
|
293
|
-
|
|
270
|
+
if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
|
|
271
|
+
parseEndTag('', lastTag);
|
|
272
|
+
}
|
|
294
273
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
274
|
+
if (!handler.html5) {
|
|
275
|
+
while (lastTag && inline(lastTag)) {
|
|
276
|
+
parseEndTag('', lastTag);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
298
279
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (arguments[4] === '') { arguments[4] = undefined; }
|
|
303
|
-
if (arguments[5] === '') { arguments[5] = undefined; }
|
|
304
|
-
}
|
|
280
|
+
if (closeSelf(tagName) && lastTag === tagName) {
|
|
281
|
+
parseEndTag('', tagName);
|
|
282
|
+
}
|
|
305
283
|
|
|
306
|
-
|
|
307
|
-
if ( name ) {
|
|
308
|
-
customAssign = arguments[2];
|
|
309
|
-
fallbackValue = arguments[3];
|
|
310
|
-
value = fallbackValue || arguments[4] || arguments[5];
|
|
284
|
+
var unary = empty(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
|
|
311
285
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
286
|
+
var attrs = match.attrs.map(function(args) {
|
|
287
|
+
var name, value, customOpen, customClose, customAssign, quote;
|
|
288
|
+
var ncp = 7; // number of captured parts, scalar
|
|
289
|
+
|
|
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] === '') { delete args[3]; }
|
|
293
|
+
if (args[4] === '') { delete args[4]; }
|
|
294
|
+
if (args[5] === '') { delete args[5]; }
|
|
295
|
+
}
|
|
316
296
|
|
|
297
|
+
function populate(index) {
|
|
298
|
+
customAssign = args[index];
|
|
299
|
+
value = args[index + 1];
|
|
300
|
+
if (typeof value !== 'undefined') {
|
|
301
|
+
return '"';
|
|
317
302
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
customAssign = arguments[i * ncp + 8];
|
|
322
|
-
if ( name ) {
|
|
323
|
-
fallbackValue = arguments[i * ncp + 9];
|
|
324
|
-
value = fallbackValue
|
|
325
|
-
|| arguments[i * ncp + 10]
|
|
326
|
-
|| arguments[i * ncp + 11];
|
|
327
|
-
customOpen = arguments[i * ncp + 6];
|
|
328
|
-
customClose = arguments[i * ncp + 12];
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
303
|
+
value = args[index + 2];
|
|
304
|
+
if (typeof value !== 'undefined') {
|
|
305
|
+
return '\'';
|
|
332
306
|
}
|
|
333
|
-
|
|
334
|
-
if ( value === undefined ) {
|
|
335
|
-
value =
|
|
307
|
+
value = args[index + 3];
|
|
308
|
+
if (typeof value === 'undefined' && fillAttrs(name)) {
|
|
309
|
+
value = name;
|
|
336
310
|
}
|
|
311
|
+
return '';
|
|
312
|
+
}
|
|
337
313
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
stack.push( { tag: tagName, attrs: attrs } );
|
|
314
|
+
var j = 1;
|
|
315
|
+
if (handler.customAttrSurround) {
|
|
316
|
+
for (var i = 0, l = handler.customAttrSurround.length; i < l; i++, j += ncp) {
|
|
317
|
+
name = args[j + 1];
|
|
318
|
+
if (name) {
|
|
319
|
+
quote = populate(j + 2);
|
|
320
|
+
customOpen = args[j];
|
|
321
|
+
customClose = args[j + 6];
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
350
325
|
}
|
|
351
|
-
|
|
352
|
-
|
|
326
|
+
|
|
327
|
+
if (!name && (name = args[j])) {
|
|
328
|
+
quote = populate(j + 1);
|
|
353
329
|
}
|
|
354
330
|
|
|
331
|
+
return {
|
|
332
|
+
name: name,
|
|
333
|
+
value: value,
|
|
334
|
+
customAssign: customAssign || '=',
|
|
335
|
+
customOpen: customOpen || '',
|
|
336
|
+
customClose: customClose || '',
|
|
337
|
+
quote: quote || ''
|
|
338
|
+
};
|
|
339
|
+
});
|
|
355
340
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
341
|
+
if (!unary) {
|
|
342
|
+
stack.push({ tag: tagName, attrs: attrs });
|
|
343
|
+
lastTag = tagName;
|
|
344
|
+
unarySlash = '';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (handler.start) {
|
|
348
|
+
handler.start(tagName, attrs, unary, unarySlash);
|
|
359
349
|
}
|
|
350
|
+
}
|
|
360
351
|
|
|
361
|
-
|
|
362
|
-
|
|
352
|
+
function parseEndTag(tag, tagName) {
|
|
353
|
+
var pos;
|
|
363
354
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
var needle = tagName.toLowerCase();
|
|
371
|
-
for ( pos = stack.length - 1; pos >= 0; pos-- ) {
|
|
372
|
-
if ( stack[ pos ].tag.toLowerCase() === needle ) {
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
355
|
+
// Find the closest opened tag of the same type
|
|
356
|
+
if (tagName) {
|
|
357
|
+
var needle = tagName.toLowerCase();
|
|
358
|
+
for (pos = stack.length - 1; pos >= 0; pos--) {
|
|
359
|
+
if (stack[pos].tag.toLowerCase() === needle) {
|
|
360
|
+
break;
|
|
375
361
|
}
|
|
376
362
|
}
|
|
363
|
+
}
|
|
364
|
+
// If no tag name is provided, clean shop
|
|
365
|
+
else {
|
|
366
|
+
pos = 0;
|
|
367
|
+
}
|
|
377
368
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
369
|
+
if (pos >= 0) {
|
|
370
|
+
// Close all the open elements, up the stack
|
|
371
|
+
for (var i = stack.length - 1; i >= pos; i--) {
|
|
372
|
+
if (handler.end) {
|
|
373
|
+
handler.end(stack[i].tag, stack[i].attrs, i > pos || !tag);
|
|
384
374
|
}
|
|
375
|
+
}
|
|
385
376
|
|
|
386
|
-
|
|
387
|
-
|
|
377
|
+
// Remove the open elements from the stack
|
|
378
|
+
stack.length = pos;
|
|
379
|
+
lastTag = pos && stack[pos - 1].tag;
|
|
380
|
+
}
|
|
381
|
+
else if (tagName.toLowerCase() === 'br') {
|
|
382
|
+
if (handler.start) {
|
|
383
|
+
handler.start(tagName, [], true, '');
|
|
388
384
|
}
|
|
389
385
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
386
|
+
else if (tagName.toLowerCase() === 'p') {
|
|
387
|
+
if (handler.start) {
|
|
388
|
+
handler.start(tagName, [], false, '', true);
|
|
389
|
+
}
|
|
390
|
+
if (handler.end) {
|
|
391
|
+
handler.end(tagName, []);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
394
396
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
exports.HTMLParser = HTMLParser;
|
|
398
|
+
exports.HTMLtoXML = function(html) {
|
|
399
|
+
var results = '';
|
|
398
400
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
401
|
+
new HTMLParser(html, {
|
|
402
|
+
start: function(tag, attrs, unary) {
|
|
403
|
+
results += '<' + tag;
|
|
402
404
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
end: function( tag ) {
|
|
406
|
-
results += '</' + tag + '>';
|
|
407
|
-
},
|
|
408
|
-
chars: function( text ) {
|
|
409
|
-
results += text;
|
|
410
|
-
},
|
|
411
|
-
comment: function( text ) {
|
|
412
|
-
results += '<!--' + text + '-->';
|
|
413
|
-
},
|
|
414
|
-
ignore: function(text) {
|
|
415
|
-
results += text;
|
|
405
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
406
|
+
results += ' ' + attrs[i].name + '="' + (attrs[i].value || '').replace(/"/g, '"') + '"';
|
|
416
407
|
}
|
|
417
|
-
});
|
|
418
408
|
|
|
419
|
-
|
|
420
|
-
|
|
409
|
+
results += (unary ? '/' : '') + '>';
|
|
410
|
+
},
|
|
411
|
+
end: function(tag) {
|
|
412
|
+
results += '</' + tag + '>';
|
|
413
|
+
},
|
|
414
|
+
chars: function(text) {
|
|
415
|
+
results += text;
|
|
416
|
+
},
|
|
417
|
+
comment: function(text) {
|
|
418
|
+
results += '<!--' + text + '-->';
|
|
419
|
+
},
|
|
420
|
+
ignore: function(text) {
|
|
421
|
+
results += text;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
421
424
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
var one = makeMap('html,head,body,title');
|
|
425
|
+
return results;
|
|
426
|
+
};
|
|
425
427
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
428
|
+
exports.HTMLtoDOM = function(html, doc) {
|
|
429
|
+
// There can be only one of these elements
|
|
430
|
+
var one = {
|
|
431
|
+
html: true,
|
|
432
|
+
head: true,
|
|
433
|
+
body: true,
|
|
434
|
+
title: true
|
|
435
|
+
};
|
|
431
436
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
doc = document.implementation.createDocument('', '', null);
|
|
438
|
-
}
|
|
439
|
-
else if ( typeof ActiveX !== 'undefined' ) {
|
|
440
|
-
doc = new ActiveXObject('Msxml.DOMDocument');
|
|
441
|
-
}
|
|
437
|
+
// Enforce a structure for the document
|
|
438
|
+
var structure = {
|
|
439
|
+
link: 'head',
|
|
440
|
+
base: 'head'
|
|
441
|
+
};
|
|
442
442
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
443
|
+
if (doc) {
|
|
444
|
+
doc = doc.ownerDocument || doc.getOwnerDocument && doc.getOwnerDocument() || doc;
|
|
445
|
+
}
|
|
446
|
+
else if (typeof DOMDocument !== 'undefined') {
|
|
447
|
+
doc = new DOMDocument();
|
|
448
|
+
}
|
|
449
|
+
else if (typeof document !== 'undefined' && document.implementation && document.implementation.createDocument) {
|
|
450
|
+
doc = document.implementation.createDocument('', '', null);
|
|
451
|
+
}
|
|
452
|
+
else if (typeof ActiveX !== 'undefined') {
|
|
453
|
+
doc = new ActiveXObject('Msxml.DOMDocument');
|
|
454
|
+
}
|
|
449
455
|
|
|
450
|
-
|
|
456
|
+
var elems = [],
|
|
451
457
|
documentElement = doc.documentElement ||
|
|
452
458
|
doc.getDocumentElement && doc.getDocumentElement();
|
|
453
459
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
460
|
+
// If we're dealing with an empty document then we
|
|
461
|
+
// need to pre-populate it with the HTML document structure
|
|
462
|
+
if (!documentElement && doc.createElement) {
|
|
463
|
+
(function() {
|
|
464
|
+
var html = doc.createElement('html');
|
|
465
|
+
var head = doc.createElement('head');
|
|
466
|
+
head.appendChild(doc.createElement('title'));
|
|
467
|
+
html.appendChild(head);
|
|
468
|
+
html.appendChild(doc.createElement('body'));
|
|
469
|
+
doc.appendChild(html);
|
|
470
|
+
})();
|
|
471
|
+
}
|
|
466
472
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
473
|
+
// Find all the unique elements
|
|
474
|
+
if (doc.getElementsByTagName) {
|
|
475
|
+
for (var i in one) {
|
|
476
|
+
one[i] = doc.getElementsByTagName(i)[0];
|
|
472
477
|
}
|
|
478
|
+
}
|
|
473
479
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
var elem = doc.createElement( tagName );
|
|
488
|
-
|
|
489
|
-
for ( var attr in attrs ) {
|
|
490
|
-
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
|
|
491
|
-
}
|
|
480
|
+
// If we're working with a document, inject contents into
|
|
481
|
+
// the body element
|
|
482
|
+
var curParentNode = one.body;
|
|
483
|
+
|
|
484
|
+
new HTMLParser(html, {
|
|
485
|
+
start: function(tagName, attrs, unary) {
|
|
486
|
+
// If it's a pre-built element, then we can ignore
|
|
487
|
+
// its construction
|
|
488
|
+
if (one[tagName]) {
|
|
489
|
+
curParentNode = one[tagName];
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
492
|
|
|
493
|
-
|
|
494
|
-
one[ structure[ tagName ] ].appendChild( elem );
|
|
495
|
-
}
|
|
496
|
-
else if ( curParentNode && curParentNode.appendChild ) {
|
|
497
|
-
curParentNode.appendChild( elem );
|
|
498
|
-
}
|
|
493
|
+
var elem = doc.createElement(tagName);
|
|
499
494
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
curParentNode = elem;
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
end: function( /* tag */ ) {
|
|
506
|
-
elems.length -= 1;
|
|
507
|
-
|
|
508
|
-
// Init the new parentNode
|
|
509
|
-
curParentNode = elems[ elems.length - 1 ];
|
|
510
|
-
},
|
|
511
|
-
chars: function( text ) {
|
|
512
|
-
curParentNode.appendChild( doc.createTextNode( text ) );
|
|
513
|
-
},
|
|
514
|
-
comment: function( /*text*/ ) {
|
|
515
|
-
// create comment node
|
|
516
|
-
},
|
|
517
|
-
ignore: function( /* text */ ) {
|
|
518
|
-
// What to do here?
|
|
495
|
+
for (var attr in attrs) {
|
|
496
|
+
elem.setAttribute(attrs[attr].name, attrs[attr].value);
|
|
519
497
|
}
|
|
520
|
-
});
|
|
521
498
|
|
|
522
|
-
|
|
523
|
-
|
|
499
|
+
if (structure[tagName] && typeof one[structure[tagName]] !== 'boolean') {
|
|
500
|
+
one[structure[tagName]].appendChild(elem);
|
|
501
|
+
}
|
|
502
|
+
else if (curParentNode && curParentNode.appendChild) {
|
|
503
|
+
curParentNode.appendChild(elem);
|
|
504
|
+
}
|
|
524
505
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
506
|
+
if (!unary) {
|
|
507
|
+
elems.push(elem);
|
|
508
|
+
curParentNode = elem;
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
end: function(/* tag */) {
|
|
512
|
+
elems.length -= 1;
|
|
513
|
+
|
|
514
|
+
// Init the new parentNode
|
|
515
|
+
curParentNode = elems[elems.length - 1];
|
|
516
|
+
},
|
|
517
|
+
chars: function(text) {
|
|
518
|
+
curParentNode.appendChild(doc.createTextNode(text));
|
|
519
|
+
},
|
|
520
|
+
comment: function(/* text */) {
|
|
521
|
+
// create comment node
|
|
522
|
+
},
|
|
523
|
+
ignore: function(/* text */) {
|
|
524
|
+
// What to do here?
|
|
530
525
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
return doc;
|
|
529
|
+
};
|