1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function style_html(html_source, options) {
|
40 |
|
41 |
|
42 | var multi_parser,
|
43 | indent_size,
|
44 | indent_character,
|
45 | max_char,
|
46 | brace_style,
|
47 | unformatted;
|
48 |
|
49 | options = options || {};
|
50 | indent_size = options.indent_size || 4;
|
51 | indent_character = options.indent_char || ' ';
|
52 | brace_style = options.brace_style || 'collapse';
|
53 | max_char = options.max_char == 0 ? Infinity : options.max_char || 70;
|
54 | unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
55 |
|
56 | function Parser() {
|
57 |
|
58 | this.pos = 0;
|
59 | this.token = '';
|
60 | this.current_mode = 'CONTENT';
|
61 | this.tags = {
|
62 | parent: 'parent1',
|
63 | parentcount: 1,
|
64 | parent1: ''
|
65 | };
|
66 | this.tag_type = '';
|
67 | this.token_text = this.last_token = this.last_text = this.token_type = '';
|
68 |
|
69 | this.Utils = {
|
70 | whitespace: "\n\r\t ".split(''),
|
71 | single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','),
|
72 | extra_liners: 'head,body,/html'.split(','),
|
73 | in_array: function (what, arr) {
|
74 | for (var i=0; i<arr.length; i++) {
|
75 | if (what === arr[i]) {
|
76 | return true;
|
77 | }
|
78 | }
|
79 | return false;
|
80 | }
|
81 | }
|
82 |
|
83 | this.get_content = function () {
|
84 |
|
85 | var input_char = '',
|
86 | content = [],
|
87 | space = false;
|
88 |
|
89 | while (this.input.charAt(this.pos) !== '<') {
|
90 | if (this.pos >= this.input.length) {
|
91 | return content.length?content.join(''):['', 'TK_EOF'];
|
92 | }
|
93 |
|
94 | input_char = this.input.charAt(this.pos);
|
95 | this.pos++;
|
96 | this.line_char_count++;
|
97 |
|
98 | if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
|
99 | if (content.length) {
|
100 | space = true;
|
101 | }
|
102 | this.line_char_count--;
|
103 | continue;
|
104 | }
|
105 | else if (space) {
|
106 | if (this.line_char_count >= this.max_char) {
|
107 | content.push('\n');
|
108 | for (var i=0; i<this.indent_level; i++) {
|
109 | content.push(this.indent_string);
|
110 | }
|
111 | this.line_char_count = 0;
|
112 | }
|
113 | else{
|
114 | content.push(' ');
|
115 | this.line_char_count++;
|
116 | }
|
117 | space = false;
|
118 | }
|
119 | content.push(input_char);
|
120 | }
|
121 | return content.length?content.join(''):'';
|
122 | }
|
123 |
|
124 | this.get_contents_to = function (name) {
|
125 | if (this.pos == this.input.length) {
|
126 | return ['', 'TK_EOF'];
|
127 | }
|
128 | var input_char = '';
|
129 | var content = '';
|
130 | var reg_match = new RegExp('\<\/' + name + '\\s*\>', 'igm');
|
131 | reg_match.lastIndex = this.pos;
|
132 | var reg_array = reg_match.exec(this.input);
|
133 | var end_script = reg_array?reg_array.index:this.input.length;
|
134 | if(this.pos < end_script) {
|
135 | content = this.input.substring(this.pos, end_script);
|
136 | this.pos = end_script;
|
137 | }
|
138 | return content;
|
139 | }
|
140 |
|
141 | this.record_tag = function (tag){
|
142 | if (this.tags[tag + 'count']) {
|
143 | this.tags[tag + 'count']++;
|
144 | this.tags[tag + this.tags[tag + 'count']] = this.indent_level;
|
145 | }
|
146 | else {
|
147 | this.tags[tag + 'count'] = 1;
|
148 | this.tags[tag + this.tags[tag + 'count']] = this.indent_level;
|
149 | }
|
150 | this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent;
|
151 | this.tags.parent = tag + this.tags[tag + 'count'];
|
152 | }
|
153 |
|
154 | this.retrieve_tag = function (tag) {
|
155 | if (this.tags[tag + 'count']) {
|
156 | var temp_parent = this.tags.parent;
|
157 | while (temp_parent) {
|
158 | if (tag + this.tags[tag + 'count'] === temp_parent) {
|
159 | break;
|
160 | }
|
161 | temp_parent = this.tags[temp_parent + 'parent'];
|
162 | }
|
163 | if (temp_parent) {
|
164 | this.indent_level = this.tags[tag + this.tags[tag + 'count']];
|
165 | this.tags.parent = this.tags[temp_parent + 'parent'];
|
166 | }
|
167 | delete this.tags[tag + this.tags[tag + 'count'] + 'parent'];
|
168 | delete this.tags[tag + this.tags[tag + 'count']];
|
169 | if (this.tags[tag + 'count'] == 1) {
|
170 | delete this.tags[tag + 'count'];
|
171 | }
|
172 | else {
|
173 | this.tags[tag + 'count']--;
|
174 | }
|
175 | }
|
176 | }
|
177 |
|
178 | this.get_tag = function () {
|
179 | var input_char = '',
|
180 | content = [],
|
181 | space = false,
|
182 | tag_start, tag_end;
|
183 |
|
184 | do {
|
185 | if (this.pos >= this.input.length) {
|
186 | return content.length?content.join(''):['', 'TK_EOF'];
|
187 | }
|
188 |
|
189 | input_char = this.input.charAt(this.pos);
|
190 | this.pos++;
|
191 | this.line_char_count++;
|
192 |
|
193 | if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
|
194 | space = true;
|
195 | this.line_char_count--;
|
196 | continue;
|
197 | }
|
198 |
|
199 | if (input_char === "'" || input_char === '"') {
|
200 | if (!content[1] || content[1] !== '!') {
|
201 | input_char += this.get_unformatted(input_char);
|
202 | space = true;
|
203 | }
|
204 | }
|
205 |
|
206 | if (input_char === '=') {
|
207 | space = false;
|
208 | }
|
209 |
|
210 | if (content.length && content[content.length-1] !== '=' && input_char !== '>'
|
211 | && space) {
|
212 | if (this.line_char_count >= this.max_char) {
|
213 | this.print_newline(false, content);
|
214 | this.line_char_count = 0;
|
215 | }
|
216 | else {
|
217 | content.push(' ');
|
218 | this.line_char_count++;
|
219 | }
|
220 | space = false;
|
221 | }
|
222 | if (input_char === '<') {
|
223 | tag_start = this.pos - 1;
|
224 | }
|
225 | content.push(input_char);
|
226 | } while (input_char !== '>');
|
227 |
|
228 | var tag_complete = content.join('');
|
229 | var tag_index;
|
230 | if (tag_complete.indexOf(' ') != -1) {
|
231 | tag_index = tag_complete.indexOf(' ');
|
232 | }
|
233 | else {
|
234 | tag_index = tag_complete.indexOf('>');
|
235 | }
|
236 | var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
|
237 | if (tag_complete.charAt(tag_complete.length-2) === '/' ||
|
238 | this.Utils.in_array(tag_check, this.Utils.single_token)) {
|
239 | this.tag_type = 'SINGLE';
|
240 | }
|
241 | else if (tag_check === 'script') {
|
242 | this.record_tag(tag_check);
|
243 | this.tag_type = 'SCRIPT';
|
244 | }
|
245 | else if (tag_check === 'style') {
|
246 | this.record_tag(tag_check);
|
247 | this.tag_type = 'STYLE';
|
248 | }
|
249 | else if (this.Utils.in_array(tag_check, unformatted)) {
|
250 | var comment = this.get_unformatted('</'+tag_check+'>', tag_complete);
|
251 | content.push(comment);
|
252 |
|
253 | if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){
|
254 | content.splice(0, 0, this.input.charAt(tag_start - 1));
|
255 | }
|
256 | tag_end = this.pos - 1;
|
257 | if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){
|
258 | content.push(this.input.charAt(tag_end + 1));
|
259 | }
|
260 | this.tag_type = 'SINGLE';
|
261 | }
|
262 | else if (tag_check.charAt(0) === '!') {
|
263 | if (tag_check.indexOf('[if') != -1) {
|
264 | if (tag_complete.indexOf('!IE') != -1) {
|
265 | var comment = this.get_unformatted('-->', tag_complete);
|
266 | content.push(comment);
|
267 | }
|
268 | this.tag_type = 'START';
|
269 | }
|
270 | else if (tag_check.indexOf('[endif') != -1) {
|
271 | this.tag_type = 'END';
|
272 | this.unindent();
|
273 | }
|
274 | else if (tag_check.indexOf('[cdata[') != -1) {
|
275 | var comment = this.get_unformatted(']]>', tag_complete);
|
276 | content.push(comment);
|
277 | this.tag_type = 'SINGLE';
|
278 | }
|
279 | else {
|
280 | var comment = this.get_unformatted('-->', tag_complete);
|
281 | content.push(comment);
|
282 | this.tag_type = 'SINGLE';
|
283 | }
|
284 | }
|
285 | else {
|
286 | if (tag_check.charAt(0) === '/') {
|
287 | this.retrieve_tag(tag_check.substring(1));
|
288 | this.tag_type = 'END';
|
289 | }
|
290 | else {
|
291 | this.record_tag(tag_check);
|
292 | this.tag_type = 'START';
|
293 | }
|
294 | if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) {
|
295 | this.print_newline(true, this.output);
|
296 | }
|
297 | }
|
298 | return content.join('');
|
299 | }
|
300 |
|
301 | this.get_unformatted = function (delimiter, orig_tag) {
|
302 |
|
303 | if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) != -1) {
|
304 | return '';
|
305 | }
|
306 | var input_char = '';
|
307 | var content = '';
|
308 | var space = true;
|
309 | do {
|
310 |
|
311 | if (this.pos >= this.input.length) {
|
312 | return content;
|
313 | }
|
314 |
|
315 | input_char = this.input.charAt(this.pos);
|
316 | this.pos++
|
317 |
|
318 | if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
|
319 | if (!space) {
|
320 | this.line_char_count--;
|
321 | continue;
|
322 | }
|
323 | if (input_char === '\n' || input_char === '\r') {
|
324 | content += '\n';
|
325 | |
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | this.line_char_count = 0;
|
332 | continue;
|
333 | }
|
334 | }
|
335 | content += input_char;
|
336 | this.line_char_count++;
|
337 | space = true;
|
338 |
|
339 |
|
340 | } while (content.toLowerCase().indexOf(delimiter) == -1);
|
341 | return content;
|
342 | }
|
343 |
|
344 | this.get_token = function () {
|
345 | var token;
|
346 |
|
347 | if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') {
|
348 | var type = this.last_token.substr(7)
|
349 | token = this.get_contents_to(type);
|
350 | if (typeof token !== 'string') {
|
351 | return token;
|
352 | }
|
353 | return [token, 'TK_' + type];
|
354 | }
|
355 | if (this.current_mode === 'CONTENT') {
|
356 | token = this.get_content();
|
357 | if (typeof token !== 'string') {
|
358 | return token;
|
359 | }
|
360 | else {
|
361 | return [token, 'TK_CONTENT'];
|
362 | }
|
363 | }
|
364 |
|
365 | if (this.current_mode === 'TAG') {
|
366 | token = this.get_tag();
|
367 | if (typeof token !== 'string') {
|
368 | return token;
|
369 | }
|
370 | else {
|
371 | var tag_name_type = 'TK_TAG_' + this.tag_type;
|
372 | return [token, tag_name_type];
|
373 | }
|
374 | }
|
375 | }
|
376 |
|
377 | this.get_full_indent = function (level) {
|
378 | level = this.indent_level + level || 0;
|
379 | if (level < 1)
|
380 | return '';
|
381 |
|
382 | return Array(level + 1).join(this.indent_string);
|
383 | }
|
384 |
|
385 |
|
386 | this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) {
|
387 |
|
388 | this.input = js_source || '';
|
389 | this.output = [];
|
390 | this.indent_character = indent_character;
|
391 | this.indent_string = '';
|
392 | this.indent_size = indent_size;
|
393 | this.brace_style = brace_style;
|
394 | this.indent_level = 0;
|
395 | this.max_char = max_char;
|
396 | this.line_char_count = 0;
|
397 |
|
398 | for (var i=0; i<this.indent_size; i++) {
|
399 | this.indent_string += this.indent_character;
|
400 | }
|
401 |
|
402 | this.print_newline = function (ignore, arr) {
|
403 | this.line_char_count = 0;
|
404 | if (!arr || !arr.length) {
|
405 | return;
|
406 | }
|
407 | if (!ignore) {
|
408 | while (this.Utils.in_array(arr[arr.length-1], this.Utils.whitespace)) {
|
409 | arr.pop();
|
410 | }
|
411 | }
|
412 | arr.push('\n');
|
413 | for (var i=0; i<this.indent_level; i++) {
|
414 | arr.push(this.indent_string);
|
415 | }
|
416 | }
|
417 |
|
418 | this.print_token = function (text) {
|
419 | this.output.push(text);
|
420 | }
|
421 |
|
422 | this.indent = function () {
|
423 | this.indent_level++;
|
424 | }
|
425 |
|
426 | this.unindent = function () {
|
427 | if (this.indent_level > 0) {
|
428 | this.indent_level--;
|
429 | }
|
430 | }
|
431 | }
|
432 | return this;
|
433 | }
|
434 |
|
435 |
|
436 |
|
437 | multi_parser = new Parser();
|
438 | multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style);
|
439 |
|
440 | while (true) {
|
441 | var t = multi_parser.get_token();
|
442 | multi_parser.token_text = t[0];
|
443 | multi_parser.token_type = t[1];
|
444 |
|
445 | if (multi_parser.token_type === 'TK_EOF') {
|
446 | break;
|
447 | }
|
448 |
|
449 | switch (multi_parser.token_type) {
|
450 | case 'TK_TAG_START':
|
451 | multi_parser.print_newline(false, multi_parser.output);
|
452 | multi_parser.print_token(multi_parser.token_text);
|
453 | multi_parser.indent();
|
454 | multi_parser.current_mode = 'CONTENT';
|
455 | break;
|
456 | case 'TK_TAG_STYLE':
|
457 | case 'TK_TAG_SCRIPT':
|
458 | multi_parser.print_newline(false, multi_parser.output);
|
459 | multi_parser.print_token(multi_parser.token_text);
|
460 | multi_parser.current_mode = 'CONTENT';
|
461 | break;
|
462 | case 'TK_TAG_END':
|
463 |
|
464 | if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
|
465 | var tag_name = multi_parser.token_text.match(/\w+/)[0];
|
466 | var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
|
467 | if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name)
|
468 | multi_parser.print_newline(true, multi_parser.output);
|
469 | }
|
470 | multi_parser.print_token(multi_parser.token_text);
|
471 | multi_parser.current_mode = 'CONTENT';
|
472 | break;
|
473 | case 'TK_TAG_SINGLE':
|
474 |
|
475 | var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
|
476 | if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
|
477 | multi_parser.print_newline(false, multi_parser.output);
|
478 | }
|
479 | multi_parser.print_token(multi_parser.token_text);
|
480 | multi_parser.current_mode = 'CONTENT';
|
481 | break;
|
482 | case 'TK_CONTENT':
|
483 | if (multi_parser.token_text !== '') {
|
484 | multi_parser.print_token(multi_parser.token_text);
|
485 | }
|
486 | multi_parser.current_mode = 'TAG';
|
487 | break;
|
488 | case 'TK_STYLE':
|
489 | case 'TK_SCRIPT':
|
490 | if (multi_parser.token_text !== '') {
|
491 | multi_parser.output.push('\n');
|
492 | var text = multi_parser.token_text;
|
493 | if (multi_parser.token_type == 'TK_SCRIPT') {
|
494 | var _beautifier = typeof js_beautify == 'function' && js_beautify;
|
495 | } else if (multi_parser.token_type == 'TK_STYLE') {
|
496 | var _beautifier = typeof css_beautify == 'function' && css_beautify;
|
497 | }
|
498 |
|
499 | if (options.indent_scripts == "keep") {
|
500 | var script_indent_level = 0;
|
501 | } else if (options.indent_scripts == "separate") {
|
502 | var script_indent_level = -multi_parser.indent_level;
|
503 | } else {
|
504 | var script_indent_level = 1;
|
505 | }
|
506 |
|
507 | var indentation = multi_parser.get_full_indent(script_indent_level);
|
508 | if (_beautifier) {
|
509 |
|
510 | text = _beautifier(text.replace(/^\s*/, indentation), options);
|
511 | } else {
|
512 |
|
513 | var white = text.match(/^\s*/)[0];
|
514 | var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
|
515 | var reindent = multi_parser.get_full_indent(script_indent_level -_level);
|
516 | text = text.replace(/^\s*/, indentation)
|
517 | .replace(/\r\n|\r|\n/g, '\n' + reindent)
|
518 | .replace(/\s*$/, '');
|
519 | }
|
520 | if (text) {
|
521 | multi_parser.print_token(text);
|
522 | multi_parser.print_newline(true, multi_parser.output);
|
523 | }
|
524 | }
|
525 | multi_parser.current_mode = 'TAG';
|
526 | break;
|
527 | }
|
528 | multi_parser.last_token = multi_parser.token_type;
|
529 | multi_parser.last_text = multi_parser.token_text;
|
530 | }
|
531 | return multi_parser.output.join('');
|
532 | }
|
533 |
|
534 | module.exports = {
|
535 | prettyPrint: style_html
|
536 | }; |
\ | No newline at end of file |