Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50641 views
1
/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
2
/*
3
4
The MIT License (MIT)
5
6
Copyright (c) 2007-2013 Einar Lielmanis and contributors.
7
8
Permission is hereby granted, free of charge, to any person
9
obtaining a copy of this software and associated documentation files
10
(the "Software"), to deal in the Software without restriction,
11
including without limitation the rights to use, copy, modify, merge,
12
publish, distribute, sublicense, and/or sell copies of the Software,
13
and to permit persons to whom the Software is furnished to do so,
14
subject to the following conditions:
15
16
The above copyright notice and this permission notice shall be
17
included in all copies or substantial portions of the Software.
18
19
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
SOFTWARE.
27
28
29
Style HTML
30
---------------
31
32
Written by Nochum Sossonko, ([email protected])
33
34
Based on code initially developed by: Einar Lielmanis, <[email protected]>
35
http://jsbeautifier.org/
36
37
Usage:
38
style_html(html_source);
39
40
style_html(html_source, options);
41
42
The options are:
43
indent_inner_html (default false) — indent <head> and <body> sections,
44
indent_size (default 4) — indentation size,
45
indent_char (default space) — character to indent with,
46
wrap_line_length (default 250) - maximum amount of characters per line (0 = disable)
47
brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
48
put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are.
49
unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
50
indent_scripts (default normal) - "keep"|"separate"|"normal"
51
preserve_newlines (default true) - whether existing line breaks before elements should be preserved
52
Only works before elements, not inside tags or for text.
53
max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
54
indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
55
end_with_newline (false) - end with a newline
56
57
58
e.g.
59
60
style_html(html_source, {
61
'indent_inner_html': false,
62
'indent_size': 2,
63
'indent_char': ' ',
64
'wrap_line_length': 78,
65
'brace_style': 'expand',
66
'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'],
67
'preserve_newlines': true,
68
'max_preserve_newlines': 5,
69
'indent_handlebars': false
70
});
71
*/
72
73
(function() {
74
75
function trim(s) {
76
return s.replace(/^\s+|\s+$/g, '');
77
}
78
79
function ltrim(s) {
80
return s.replace(/^\s+/g, '');
81
}
82
83
function rtrim(s) {
84
return s.replace(/\s+$/g,'');
85
}
86
87
function style_html(html_source, options, js_beautify, css_beautify) {
88
//Wrapper function to invoke all the necessary constructors and deal with the output.
89
90
var multi_parser,
91
indent_inner_html,
92
indent_size,
93
indent_character,
94
wrap_line_length,
95
brace_style,
96
unformatted,
97
preserve_newlines,
98
max_preserve_newlines,
99
indent_handlebars,
100
end_with_newline;
101
102
options = options || {};
103
104
// backwards compatibility to 1.3.4
105
if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
106
(options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
107
options.wrap_line_length = options.max_char;
108
}
109
110
indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
111
indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
112
indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
113
brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
114
wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
115
unformatted = options.unformatted || ['a', 'span', 'img', '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'];
116
preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
117
max_preserve_newlines = preserve_newlines ?
118
(isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10))
119
: 0;
120
indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
121
end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
122
123
function Parser() {
124
125
this.pos = 0; //Parser position
126
this.token = '';
127
this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
128
this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
129
parent: 'parent1',
130
parentcount: 1,
131
parent1: ''
132
};
133
this.tag_type = '';
134
this.token_text = this.last_token = this.last_text = this.token_type = '';
135
this.newlines = 0;
136
this.indent_content = indent_inner_html;
137
138
this.Utils = { //Uilities made available to the various functions
139
whitespace: "\n\r\t ".split(''),
140
single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
141
extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
142
in_array: function(what, arr) {
143
for (var i = 0; i < arr.length; i++) {
144
if (what === arr[i]) {
145
return true;
146
}
147
}
148
return false;
149
}
150
};
151
152
// Return true iff the given text is composed entirely of
153
// whitespace.
154
this.is_whitespace = function(text) {
155
for (var n = 0; n < text.length; text++) {
156
if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) {
157
return false;
158
}
159
}
160
return true;
161
}
162
163
this.traverse_whitespace = function() {
164
var input_char = '';
165
166
input_char = this.input.charAt(this.pos);
167
if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
168
this.newlines = 0;
169
while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
170
if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
171
this.newlines += 1;
172
}
173
174
this.pos++;
175
input_char = this.input.charAt(this.pos);
176
}
177
return true;
178
}
179
return false;
180
};
181
182
// Append a space to the given content (string array) or, if we are
183
// at the wrap_line_length, append a newline/indentation.
184
this.space_or_wrap = function(content) {
185
if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
186
this.print_newline(false, content);
187
this.print_indentation(content);
188
} else {
189
this.line_char_count++;
190
content.push(' ');
191
}
192
};
193
194
this.get_content = function() { //function to capture regular content between tags
195
var input_char = '',
196
content = [],
197
space = false; //if a space is needed
198
199
while (this.input.charAt(this.pos) !== '<') {
200
if (this.pos >= this.input.length) {
201
return content.length ? content.join('') : ['', 'TK_EOF'];
202
}
203
204
if (this.traverse_whitespace()) {
205
this.space_or_wrap(content);
206
continue;
207
}
208
209
if (indent_handlebars) {
210
// Handlebars parsing is complicated.
211
// {{#foo}} and {{/foo}} are formatted tags.
212
// {{something}} should get treated as content, except:
213
// {{else}} specifically behaves like {{#if}} and {{/if}}
214
var peek3 = this.input.substr(this.pos, 3);
215
if (peek3 === '{{#' || peek3 === '{{/') {
216
// These are tags and not content.
217
break;
218
} else if (this.input.substr(this.pos, 2) === '{{') {
219
if (this.get_tag(true) === '{{else}}') {
220
break;
221
}
222
}
223
}
224
225
input_char = this.input.charAt(this.pos);
226
this.pos++;
227
this.line_char_count++;
228
content.push(input_char); //letter at-a-time (or string) inserted to an array
229
}
230
return content.length ? content.join('') : '';
231
};
232
233
this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
234
if (this.pos === this.input.length) {
235
return ['', 'TK_EOF'];
236
}
237
var input_char = '';
238
var content = '';
239
var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
240
reg_match.lastIndex = this.pos;
241
var reg_array = reg_match.exec(this.input);
242
var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
243
if (this.pos < end_script) { //get everything in between the script tags
244
content = this.input.substring(this.pos, end_script);
245
this.pos = end_script;
246
}
247
return content;
248
};
249
250
this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
251
if (this.tags[tag + 'count']) { //check for the existence of this tag type
252
this.tags[tag + 'count']++;
253
this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
254
} else { //otherwise initialize this tag type
255
this.tags[tag + 'count'] = 1;
256
this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
257
}
258
this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
259
this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
260
};
261
262
this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
263
if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
264
var temp_parent = this.tags.parent; //check to see if it's a closable tag.
265
while (temp_parent) { //till we reach '' (the initial value);
266
if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
267
break;
268
}
269
temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
270
}
271
if (temp_parent) { //if we caught something
272
this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
273
this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
274
}
275
delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
276
delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
277
if (this.tags[tag + 'count'] === 1) {
278
delete this.tags[tag + 'count'];
279
} else {
280
this.tags[tag + 'count']--;
281
}
282
}
283
};
284
285
this.indent_to_tag = function(tag) {
286
// Match the indentation level to the last use of this tag, but don't remove it.
287
if (!this.tags[tag + 'count']) {
288
return;
289
}
290
var temp_parent = this.tags.parent;
291
while (temp_parent) {
292
if (tag + this.tags[tag + 'count'] === temp_parent) {
293
break;
294
}
295
temp_parent = this.tags[temp_parent + 'parent'];
296
}
297
if (temp_parent) {
298
this.indent_level = this.tags[tag + this.tags[tag + 'count']];
299
}
300
};
301
302
this.get_tag = function(peek) { //function to get a full tag and parse its type
303
var input_char = '',
304
content = [],
305
comment = '',
306
space = false,
307
tag_start, tag_end,
308
tag_start_char,
309
orig_pos = this.pos,
310
orig_line_char_count = this.line_char_count;
311
312
peek = peek !== undefined ? peek : false;
313
314
do {
315
if (this.pos >= this.input.length) {
316
if (peek) {
317
this.pos = orig_pos;
318
this.line_char_count = orig_line_char_count;
319
}
320
return content.length ? content.join('') : ['', 'TK_EOF'];
321
}
322
323
input_char = this.input.charAt(this.pos);
324
this.pos++;
325
326
if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
327
space = true;
328
continue;
329
}
330
331
if (input_char === "'" || input_char === '"') {
332
input_char += this.get_unformatted(input_char);
333
space = true;
334
335
}
336
337
if (input_char === '=') { //no space before =
338
space = false;
339
}
340
341
if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
342
//no space after = or before >
343
this.space_or_wrap(content);
344
space = false;
345
}
346
347
if (indent_handlebars && tag_start_char === '<') {
348
// When inside an angle-bracket tag, put spaces around
349
// handlebars not inside of strings.
350
if ((input_char + this.input.charAt(this.pos)) === '{{') {
351
input_char += this.get_unformatted('}}');
352
if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
353
input_char = ' ' + input_char;
354
}
355
space = true;
356
}
357
}
358
359
if (input_char === '<' && !tag_start_char) {
360
tag_start = this.pos - 1;
361
tag_start_char = '<';
362
}
363
364
if (indent_handlebars && !tag_start_char) {
365
if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] == '{') {
366
if (input_char === '#' || input_char === '/') {
367
tag_start = this.pos - 3;
368
} else {
369
tag_start = this.pos - 2;
370
}
371
tag_start_char = '{';
372
}
373
}
374
375
this.line_char_count++;
376
content.push(input_char); //inserts character at-a-time (or string)
377
378
if (content[1] && content[1] === '!') { //if we're in a comment, do something special
379
// We treat all comments as literals, even more than preformatted tags
380
// we just look for the appropriate close tag
381
content = [this.get_comment(tag_start)];
382
break;
383
}
384
385
if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
386
break;
387
}
388
} while (input_char !== '>');
389
390
var tag_complete = content.join('');
391
var tag_index;
392
var tag_offset;
393
394
if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
395
tag_index = tag_complete.indexOf(' ');
396
} else if (tag_complete[0] === '{') {
397
tag_index = tag_complete.indexOf('}');
398
} else { //otherwise go with the tag ending
399
tag_index = tag_complete.indexOf('>');
400
}
401
if (tag_complete[0] === '<' || !indent_handlebars) {
402
tag_offset = 1;
403
} else {
404
tag_offset = tag_complete[2] === '#' ? 3 : 2;
405
}
406
var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
407
if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
408
this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
409
if (!peek) {
410
this.tag_type = 'SINGLE';
411
}
412
} else if (indent_handlebars && tag_complete[0] === '{' && tag_check === 'else') {
413
if (!peek) {
414
this.indent_to_tag('if');
415
this.tag_type = 'HANDLEBARS_ELSE';
416
this.indent_content = true;
417
this.traverse_whitespace();
418
}
419
} else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
420
comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
421
content.push(comment);
422
tag_end = this.pos - 1;
423
this.tag_type = 'SINGLE';
424
} else if (tag_check === 'script' &&
425
(tag_complete.search('type') === -1 ||
426
(tag_complete.search('type') > -1 &&
427
tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/) > -1))) {
428
if (!peek) {
429
this.record_tag(tag_check);
430
this.tag_type = 'SCRIPT';
431
}
432
} else if (tag_check === 'style' &&
433
(tag_complete.search('type') === -1 ||
434
(tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
435
if (!peek) {
436
this.record_tag(tag_check);
437
this.tag_type = 'STYLE';
438
}
439
} else if (tag_check.charAt(0) === '!') { //peek for <! comment
440
// for comments content is already correct.
441
if (!peek) {
442
this.tag_type = 'SINGLE';
443
this.traverse_whitespace();
444
}
445
} else if (!peek) {
446
if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
447
this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
448
this.tag_type = 'END';
449
} else { //otherwise it's a start-tag
450
this.record_tag(tag_check); //push it on the tag stack
451
if (tag_check.toLowerCase() !== 'html') {
452
this.indent_content = true;
453
}
454
this.tag_type = 'START';
455
}
456
457
// Allow preserving of newlines after a start or end tag
458
if (this.traverse_whitespace()) {
459
this.space_or_wrap(content);
460
}
461
462
if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
463
this.print_newline(false, this.output);
464
if (this.output.length && this.output[this.output.length - 2] !== '\n') {
465
this.print_newline(true, this.output);
466
}
467
}
468
}
469
470
if (peek) {
471
this.pos = orig_pos;
472
this.line_char_count = orig_line_char_count;
473
}
474
475
return content.join(''); //returns fully formatted tag
476
};
477
478
this.get_comment = function(start_pos) { //function to return comment content in its entirety
479
// this is will have very poor perf, but will work for now.
480
var comment = '',
481
delimiter = '>',
482
matched = false;
483
484
this.pos = start_pos;
485
input_char = this.input.charAt(this.pos);
486
this.pos++;
487
488
while (this.pos <= this.input.length) {
489
comment += input_char;
490
491
// only need to check for the delimiter if the last chars match
492
if (comment[comment.length - 1] === delimiter[delimiter.length - 1] &&
493
comment.indexOf(delimiter) !== -1) {
494
break;
495
}
496
497
// only need to search for custom delimiter for the first few characters
498
if (!matched && comment.length < 10) {
499
if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
500
delimiter = '<![endif]>';
501
matched = true;
502
} else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
503
delimiter = ']]>';
504
matched = true;
505
} else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
506
delimiter = ']>';
507
matched = true;
508
} else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
509
delimiter = '-->';
510
matched = true;
511
}
512
}
513
514
input_char = this.input.charAt(this.pos);
515
this.pos++;
516
}
517
518
return comment;
519
};
520
521
this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
522
523
if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
524
return '';
525
}
526
var input_char = '';
527
var content = '';
528
var min_index = 0;
529
var space = true;
530
do {
531
532
if (this.pos >= this.input.length) {
533
return content;
534
}
535
536
input_char = this.input.charAt(this.pos);
537
this.pos++;
538
539
if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
540
if (!space) {
541
this.line_char_count--;
542
continue;
543
}
544
if (input_char === '\n' || input_char === '\r') {
545
content += '\n';
546
/* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
547
for (var i=0; i<this.indent_level; i++) {
548
content += this.indent_string;
549
}
550
space = false; //...and make sure other indentation is erased
551
*/
552
this.line_char_count = 0;
553
continue;
554
}
555
}
556
content += input_char;
557
this.line_char_count++;
558
space = true;
559
560
if (indent_handlebars && input_char === '{' && content.length && content[content.length - 2] === '{') {
561
// Handlebars expressions in strings should also be unformatted.
562
content += this.get_unformatted('}}');
563
// These expressions are opaque. Ignore delimiters found in them.
564
min_index = content.length;
565
}
566
} while (content.toLowerCase().indexOf(delimiter, min_index) === -1);
567
return content;
568
};
569
570
this.get_token = function() { //initial handler for token-retrieval
571
var token;
572
573
if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
574
var type = this.last_token.substr(7);
575
token = this.get_contents_to(type);
576
if (typeof token !== 'string') {
577
return token;
578
}
579
return [token, 'TK_' + type];
580
}
581
if (this.current_mode === 'CONTENT') {
582
token = this.get_content();
583
if (typeof token !== 'string') {
584
return token;
585
} else {
586
return [token, 'TK_CONTENT'];
587
}
588
}
589
590
if (this.current_mode === 'TAG') {
591
token = this.get_tag();
592
if (typeof token !== 'string') {
593
return token;
594
} else {
595
var tag_name_type = 'TK_TAG_' + this.tag_type;
596
return [token, tag_name_type];
597
}
598
}
599
};
600
601
this.get_full_indent = function(level) {
602
level = this.indent_level + level || 0;
603
if (level < 1) {
604
return '';
605
}
606
607
return Array(level + 1).join(this.indent_string);
608
};
609
610
this.is_unformatted = function(tag_check, unformatted) {
611
//is this an HTML5 block-level link?
612
if (!this.Utils.in_array(tag_check, unformatted)) {
613
return false;
614
}
615
616
if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
617
return true;
618
}
619
620
//at this point we have an tag; is its first child something we want to remain
621
//unformatted?
622
var next_tag = this.get_tag(true /* peek. */ );
623
624
// test next_tag to see if it is just html tag (no external content)
625
var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
626
627
// if next_tag comes back but is not an isolated tag, then
628
// let's treat the 'a' tag as having content
629
// and respect the unformatted option
630
if (!tag || this.Utils.in_array(tag, unformatted)) {
631
return true;
632
} else {
633
return false;
634
}
635
};
636
637
this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
638
639
this.input = js_source || ''; //gets the input for the Parser
640
this.output = [];
641
this.indent_character = indent_character;
642
this.indent_string = '';
643
this.indent_size = indent_size;
644
this.brace_style = brace_style;
645
this.indent_level = 0;
646
this.wrap_line_length = wrap_line_length;
647
this.line_char_count = 0; //count to see if wrap_line_length was exceeded
648
649
for (var i = 0; i < this.indent_size; i++) {
650
this.indent_string += this.indent_character;
651
}
652
653
this.print_newline = function(force, arr) {
654
this.line_char_count = 0;
655
if (!arr || !arr.length) {
656
return;
657
}
658
if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
659
if ((arr[arr.length - 1] !== '\n')) {
660
arr[arr.length - 1] = rtrim(arr[arr.length - 1]);
661
}
662
arr.push('\n');
663
}
664
};
665
666
this.print_indentation = function(arr) {
667
for (var i = 0; i < this.indent_level; i++) {
668
arr.push(this.indent_string);
669
this.line_char_count += this.indent_string.length;
670
}
671
};
672
673
this.print_token = function(text) {
674
// Avoid printing initial whitespace.
675
if (this.is_whitespace(text) && !this.output.length) {
676
return;
677
}
678
if (text || text !== '') {
679
if (this.output.length && this.output[this.output.length - 1] === '\n') {
680
this.print_indentation(this.output);
681
text = ltrim(text);
682
}
683
}
684
this.print_token_raw(text);
685
};
686
687
this.print_token_raw = function(text) {
688
// If we are going to print newlines, truncate trailing
689
// whitespace, as the newlines will represent the space.
690
if (this.newlines > 0) {
691
text = rtrim(text);
692
}
693
694
if (text && text !== '') {
695
if (text.length > 1 && text[text.length - 1] === '\n') {
696
// unformatted tags can grab newlines as their last character
697
this.output.push(text.slice(0, -1));
698
this.print_newline(false, this.output);
699
} else {
700
this.output.push(text);
701
}
702
}
703
704
for (var n = 0; n < this.newlines; n++) {
705
this.print_newline(n > 0, this.output);
706
}
707
this.newlines = 0;
708
};
709
710
this.indent = function() {
711
this.indent_level++;
712
};
713
714
this.unindent = function() {
715
if (this.indent_level > 0) {
716
this.indent_level--;
717
}
718
};
719
};
720
return this;
721
}
722
723
/*_____________________--------------------_____________________*/
724
725
multi_parser = new Parser(); //wrapping functions Parser
726
multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
727
728
while (true) {
729
var t = multi_parser.get_token();
730
multi_parser.token_text = t[0];
731
multi_parser.token_type = t[1];
732
733
if (multi_parser.token_type === 'TK_EOF') {
734
break;
735
}
736
737
switch (multi_parser.token_type) {
738
case 'TK_TAG_START':
739
multi_parser.print_newline(false, multi_parser.output);
740
multi_parser.print_token(multi_parser.token_text);
741
if (multi_parser.indent_content) {
742
multi_parser.indent();
743
multi_parser.indent_content = false;
744
}
745
multi_parser.current_mode = 'CONTENT';
746
break;
747
case 'TK_TAG_STYLE':
748
case 'TK_TAG_SCRIPT':
749
multi_parser.print_newline(false, multi_parser.output);
750
multi_parser.print_token(multi_parser.token_text);
751
multi_parser.current_mode = 'CONTENT';
752
break;
753
case 'TK_TAG_END':
754
//Print new line only if the tag has no content and has child
755
if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
756
var tag_name = multi_parser.token_text.match(/\w+/)[0];
757
var tag_extracted_from_last_output = null;
758
if (multi_parser.output.length) {
759
tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
760
}
761
if (tag_extracted_from_last_output === null ||
762
tag_extracted_from_last_output[1] !== tag_name) {
763
multi_parser.print_newline(false, multi_parser.output);
764
}
765
}
766
multi_parser.print_token(multi_parser.token_text);
767
multi_parser.current_mode = 'CONTENT';
768
break;
769
case 'TK_TAG_SINGLE':
770
// Don't add a newline before elements that should remain unformatted.
771
var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i);
772
if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
773
multi_parser.print_newline(false, multi_parser.output);
774
}
775
multi_parser.print_token(multi_parser.token_text);
776
multi_parser.current_mode = 'CONTENT';
777
break;
778
case 'TK_TAG_HANDLEBARS_ELSE':
779
multi_parser.print_token(multi_parser.token_text);
780
if (multi_parser.indent_content) {
781
multi_parser.indent();
782
multi_parser.indent_content = false;
783
}
784
multi_parser.current_mode = 'CONTENT';
785
break;
786
case 'TK_CONTENT':
787
multi_parser.print_token(multi_parser.token_text);
788
multi_parser.current_mode = 'TAG';
789
break;
790
case 'TK_STYLE':
791
case 'TK_SCRIPT':
792
if (multi_parser.token_text !== '') {
793
multi_parser.print_newline(false, multi_parser.output);
794
var text = multi_parser.token_text,
795
_beautifier,
796
script_indent_level = 1;
797
if (multi_parser.token_type === 'TK_SCRIPT') {
798
_beautifier = typeof js_beautify === 'function' && js_beautify;
799
} else if (multi_parser.token_type === 'TK_STYLE') {
800
_beautifier = typeof css_beautify === 'function' && css_beautify;
801
}
802
803
if (options.indent_scripts === "keep") {
804
script_indent_level = 0;
805
} else if (options.indent_scripts === "separate") {
806
script_indent_level = -multi_parser.indent_level;
807
}
808
809
var indentation = multi_parser.get_full_indent(script_indent_level);
810
if (_beautifier) {
811
// call the Beautifier if avaliable
812
text = _beautifier(text.replace(/^\s*/, indentation), options);
813
} else {
814
// simply indent the string otherwise
815
var white = text.match(/^\s*/)[0];
816
var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
817
var reindent = multi_parser.get_full_indent(script_indent_level - _level);
818
text = text.replace(/^\s*/, indentation)
819
.replace(/\r\n|\r|\n/g, '\n' + reindent)
820
.replace(/\s+$/, '');
821
}
822
if (text) {
823
multi_parser.print_token_raw(text);
824
multi_parser.print_newline(true, multi_parser.output);
825
}
826
}
827
multi_parser.current_mode = 'TAG';
828
break;
829
default:
830
// We should not be getting here but we don't want to drop input on the floor
831
// Just output the text and move on
832
if (multi_parser.token_text !== '') {
833
multi_parser.print_token(multi_parser.token_text);
834
}
835
break;
836
}
837
multi_parser.last_token = multi_parser.token_type;
838
multi_parser.last_text = multi_parser.token_text;
839
}
840
var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, '');
841
if (end_with_newline) {
842
sweet_code += '\n';
843
}
844
return sweet_code;
845
}
846
847
if (typeof define === "function" && define.amd) {
848
// Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
849
define(["require", "./beautify", "./beautify-css"], function(requireamd) {
850
var js_beautify = requireamd("./beautify");
851
var css_beautify = requireamd("./beautify-css");
852
853
return {
854
html_beautify: function(html_source, options) {
855
return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
856
}
857
};
858
});
859
} else if (typeof exports !== "undefined") {
860
// Add support for CommonJS. Just put this file somewhere on your require.paths
861
// and you will be able to `var html_beautify = require("beautify").html_beautify`.
862
var js_beautify = require('./beautify.js');
863
var css_beautify = require('./beautify-css.js');
864
865
exports.html_beautify = function(html_source, options) {
866
return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
867
};
868
} else if (typeof window !== "undefined") {
869
// If we're running a web page and don't have either of the above, add our one global
870
window.html_beautify = function(html_source, options) {
871
return style_html(html_source, options, window.js_beautify, window.css_beautify);
872
};
873
} else if (typeof global !== "undefined") {
874
// If we don't even have window, try global.
875
global.html_beautify = function(html_source, options) {
876
return style_html(html_source, options, global.js_beautify, global.css_beautify);
877
};
878
}
879
880
}());
881
882