UNPKG

35.5 kBJavaScriptView Raw
1/**
2 * Extended from the 'marked' markdown parser, by Christopher Jeffrey.
3 * Please don't bug Chris with issues, since he was not involved in this adaptation (unless you want to give him karma)!
4 *
5 * Citations & Mad Props:
6 * - https://github.com/chjj/marked
7 */
8
9var annotext = require('./annotext');
10/**
11 * Block-Level Grammar
12 */
13
14var block = {
15 newline: /^\n+/,
16 code: /^( {4}[^\n]+\n*)+/,
17 fences: noop,
18 hr: /^( *[-*_]){3,} *(?:\n+|$)/,
19 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
20 nptable: noop,
21 lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
22 blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
23 list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
24 html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
25 def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
26 table: noop,
27 paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
28 text: /^[^\n]+/
29};
30
31block.bullet = /(?:[*+-]|\d+\.)/;
32block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
33block.item = replace(block.item, 'gm')
34(/bull/g, block.bullet)
35();
36
37block.list = replace(block.list)
38(/bull/g, block.bullet)
39('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
40();
41
42block._tag = '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
43
44block.html = replace(block.html)
45('comment', /<!--[\s\S]*?-->/)
46('closed', /<(tag)[\s\S]+?<\/\1>/)
47('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
48(/tag/g, block._tag)
49();
50
51block.paragraph = replace(block.paragraph)
52('hr', block.hr)
53('heading', block.heading)
54('lheading', block.lheading)
55('blockquote', block.blockquote)
56('tag', '<' + block._tag)
57('def', block.def)
58();
59
60/**
61 * Normal Block Grammar
62 */
63
64block.normal = merge({}, block);
65
66/**
67 * GFM Block Grammar
68 */
69
70block.gfm = merge({}, block.normal, {
71 fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
72 paragraph: /^/
73});
74
75block.gfm.paragraph = replace(block.paragraph)
76('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|' + block.list.source.replace('\\1', '\\3') + '|')
77();
78
79/**
80 * GFM + Tables Block Grammar
81 */
82
83block.tables = merge({}, block.gfm, {
84 nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
85 table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
86});
87
88/**
89 * Block Lexer
90 */
91
92function Lexer(options, annotextDoc) {
93 this.tokens = [];
94 this.tokens.links = {};
95 this.options = options || marked.defaults;
96 this.rules = block.normal;
97
98 this.isAnnotated = annotextDoc != null;
99 if (this.isAnnotated) {
100 this.annotextDoc = annotextDoc;
101 this.annotextDocParsed = annotextDoc.parse();
102 }
103
104 if (this.options.gfm) {
105 if (this.options.tables) {
106 this.rules = block.tables;
107 } else {
108 this.rules = block.gfm;
109 }
110 }
111}
112
113/**
114 * Expose Block Rules
115 */
116
117Lexer.rules = block;
118
119/**
120 * Static Lex Method
121 */
122
123Lexer.lex = function(src, annotextDoc, options) {
124 var lexer = new Lexer(options, annotextDoc);
125 return lexer.lex(src);
126};
127
128/**
129 * Preprocessing
130 */
131
132Lexer.prototype.lex = function(src) {
133 src = src
134 .replace(/\r\n|\r/g, '\n')
135 .replace(/\t/g, ' ')
136 .replace(/\u00a0/g, ' ')
137 .replace(/\u2424/g, '\n');
138
139 return this.token(src, true);
140};
141
142/**
143 * Lexing
144 */
145Lexer.prototype.token = function(src, top) {
146 var src = src.replace(/^ +$/gm, ''),
147 next, loose, cap, bull, b, item, space, i, l;
148
149 while (src) {
150 // newline
151 if (cap = this.rules.newline.exec(src)) {
152 src = src.substring(cap[0].length);
153 if (cap[0].length > 1) {
154 var tok = {
155 type: 'space'
156 };
157 if (this.isAnnotated) {
158 tok.offset = cap.index;
159 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
160 }
161 this.tokens.push(tok);
162 }
163 }
164
165 // code
166 if (cap = this.rules.code.exec(src)) {
167 src = src.substring(cap[0].length);
168 cap = cap[0].replace(/^ {4}/gm, '');
169 var tok = {
170 type: 'code',
171 text: !this.options.pedantic ? cap.replace(/\n+$/, '') : cap
172 };
173 if (this.isAnnotated) {
174 tok.offset = cap.index;
175 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
176 }
177 this.tokens.push(tok);
178 continue;
179 }
180
181 // fences (gfm)
182 if (cap = this.rules.fences.exec(src)) {
183 src = src.substring(cap[0].length);
184 var tok = {
185 type: 'code',
186 lang: cap[2],
187 text: cap[3]
188 };
189 if (this.isAnnotated) {
190 tok.offset = cap.index;
191 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
192 }
193 this.tokens.push(tok);
194 continue;
195 }
196
197 // heading
198 if (cap = this.rules.heading.exec(src)) {
199 src = src.substring(cap[0].length);
200 var tok = {
201 type: 'heading',
202 depth: cap[1].length,
203 text: cap[2]
204 };
205 if (this.isAnnotated) {
206 tok.offset = cap.index;
207 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
208 }
209 this.tokens.push(tok);
210 continue;
211 }
212
213 // table no leading pipe (gfm)
214 if (top && (cap = this.rules.nptable.exec(src))) {
215 src = src.substring(cap[0].length);
216
217 item = {
218 type: 'table',
219 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
220 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
221 cells: cap[3].replace(/\n$/, '').split('\n')
222 };
223
224 if (this.isAnnotated) {
225 item.offset = cap.index;
226 item.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
227 }
228
229 for (i = 0; i < item.align.length; i++) {
230 if (/^ *-+: *$/.test(item.align[i])) {
231 item.align[i] = 'right';
232 } else if (/^ *:-+: *$/.test(item.align[i])) {
233 item.align[i] = 'center';
234 } else if (/^ *:-+ *$/.test(item.align[i])) {
235 item.align[i] = 'left';
236 } else {
237 item.align[i] = null;
238 }
239 }
240
241 for (i = 0; i < item.cells.length; i++) {
242 item.cells[i] = item.cells[i].split(/ *\| */);
243 }
244
245 this.tokens.push(item);
246
247 continue;
248 }
249
250 // lheading
251 if (cap = this.rules.lheading.exec(src)) {
252 src = src.substring(cap[0].length);
253 var tok = {
254 type: 'heading',
255 depth: cap[2] === '=' ? 1 : 2,
256 text: cap[1]
257 };
258 if (this.isAnnotated) {
259 tok.offset = cap.index;
260 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
261 }
262 this.tokens.push(tok);
263 continue;
264 }
265
266 // hr
267 if (cap = this.rules.hr.exec(src)) {
268 src = src.substring(cap[0].length);
269 var tok = {
270 type: 'hr'
271 };
272 if (this.isAnnotated) {
273 tok.offset = cap.index;
274 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
275 }
276 this.tokens.push(tok);
277 continue;
278 }
279
280 // blockquote
281 if (cap = this.rules.blockquote.exec(src)) {
282 src = src.substring(cap[0].length);
283
284 var tok = {
285 type: 'blockquote_start'
286 };
287 if (this.isAnnotated) {
288 tok.offset = cap.index;
289 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
290 }
291 this.tokens.push(tok);
292
293 cap = cap[0].replace(/^ *> ?/gm, '');
294
295 // Pass `top` to keep the current
296 // "toplevel" state. This is exactly
297 // how markdown.pl works.
298 this.token(cap, top);
299
300 var tok = {
301 type: 'blockquote_end'
302 };
303 if (this.isAnnotated) {
304 tok.offset = cap.index;
305 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
306 }
307 this.tokens.push(tok);
308
309 continue;
310 }
311
312 // list
313 if (cap = this.rules.list.exec(src)) {
314 src = src.substring(cap[0].length);
315 bull = cap[2];
316
317 var tok = {
318 type: 'list_start',
319 ordered: bull.length > 1
320 };
321 if (this.isAnnotated) {
322 tok.offset = cap.index;
323 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
324 }
325 this.tokens.push(tok);
326
327 // Get each top-level item.
328 cap = cap[0].match(this.rules.item);
329
330 next = false;
331 l = cap.length;
332 i = 0;
333
334 for (; i < l; i++) {
335 item = cap[i];
336
337 // Remove the list item's bullet
338 // so it is seen as the next token.
339 space = item.length;
340 item = item.replace(/^ *([*+-]|\d+\.) +/, '');
341
342 // Outdent whatever the
343 // list item contains. Hacky.
344 if (~item.indexOf('\n ')) {
345 space -= item.length;
346 item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, '');
347 }
348
349 // Determine whether the next list item belongs here.
350 // Backpedal if it does not belong in this list.
351 if (this.options.smartLists && i !== l - 1) {
352 b = block.bullet.exec(cap[i + 1])[0];
353 if (bull !== b && !(bull.length > 1 && b.length > 1)) {
354 src = cap.slice(i + 1).join('\n') + src;
355 i = l - 1;
356 }
357 }
358
359 // Determine whether item is loose or not.
360 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
361 // for discount behavior.
362 loose = next || /\n\n(?!\s*$)/.test(item);
363 if (i !== l - 1) {
364 next = item.charAt(item.length - 1) === '\n';
365 if (!loose) loose = next;
366 }
367
368 var tok = {
369 type: loose ? 'loose_item_start' : 'list_item_start'
370 };
371 if (this.isAnnotated) {
372 tok.offset = cap.index;
373 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
374 }
375 this.tokens.push(tok);
376
377 // Recurse.
378 this.token(item, false);
379
380 var tok = {
381 type: 'list_item_end'
382 };
383 if (this.isAnnotated) {
384 tok.offset = cap.index;
385 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
386 }
387 this.tokens.push(tok);
388 }
389
390 var tok = {
391 type: 'list_end'
392 };
393 if (this.isAnnotated) {
394 tok.offset = cap.index;
395 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
396 }
397 this.tokens.push(tok);
398
399 continue;
400 }
401
402 // html
403 if (cap = this.rules.html.exec(src)) {
404 src = src.substring(cap[0].length);
405 var tok = {
406 type: this.options.sanitize ? 'paragraph' : 'html',
407 pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
408 text: cap[0],
409 };
410 if (this.isAnnotated) {
411 tok.offset = cap.index;
412 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
413 }
414 this.tokens.push(tok);
415 continue;
416 }
417
418 // def
419 if (top && (cap = this.rules.def.exec(src))) {
420 src = src.substring(cap[0].length);
421 var tok = {
422 href: cap[2],
423 title: cap[3]
424 };
425 if (this.isAnnotated) {
426 tok.offset = cap.index;
427 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
428 }
429 this.tokens.links[cap[1].toLowerCase()] = tok;
430 continue;
431 }
432
433 // table (gfm)
434 if (top && (cap = this.rules.table.exec(src))) {
435 src = src.substring(cap[0].length);
436
437 item = {
438 type: 'table',
439 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
440 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
441 cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
442 };
443 if (this.isAnnotated) {
444 item.offset = cap.index;
445 item.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
446 }
447
448 for (i = 0; i < item.align.length; i++) {
449 if (/^ *-+: *$/.test(item.align[i])) {
450 item.align[i] = 'right';
451 } else if (/^ *:-+: *$/.test(item.align[i])) {
452 item.align[i] = 'center';
453 } else if (/^ *:-+ *$/.test(item.align[i])) {
454 item.align[i] = 'left';
455 } else {
456 item.align[i] = null;
457 }
458 }
459
460 for (i = 0; i < item.cells.length; i++) {
461 item.cells[i] = item.cells[i]
462 .replace(/^ *\| *| *\| *$/g, '')
463 .split(/ *\| */);
464 }
465
466 this.tokens.push(item);
467
468 continue;
469 }
470
471 // top-level paragraph
472 if (top && (cap = this.rules.paragraph.exec(src))) {
473 src = src.substring(cap[0].length);
474 var tok = {
475 type: 'paragraph',
476 text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
477 };
478 if (this.isAnnotated) {
479 tok.offset = cap.index;
480 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
481 }
482 this.tokens.push(tok);
483 continue;
484 }
485
486 // text
487 if (cap = this.rules.text.exec(src)) {
488 // Top-level should never reach here.
489 src = src.substring(cap[0].length);
490 var tok = {
491 type: 'text',
492 text: cap[0]
493 };
494 if (this.isAnnotated) {
495 tok.offset = cap.index;
496 tok.revisions = this.annotextDocParsed.header.annotations.slice(cap.index, cap[0].length);
497 }
498 this.tokens.push(tok);
499 continue;
500 }
501
502 if (src) {
503 throw new
504 Error('Infinite loop on byte: ' + src.charCodeAt(0));
505 }
506 }
507
508 if (this.isAnnotated) {
509 var _self = this;
510 this.tokens.forEach(function(token) {
511 if (token.revisions != undefined && token.revisions != null) {
512 token.revisions.forEach(function(rev) {
513 rev.index += token.offset;
514 rev.content = _self.annotextDocParsed.content[rev.index];
515 });
516 }
517 });
518 }
519
520 return this.tokens;
521};
522
523/**
524 * Inline-Level Grammar
525 */
526
527var inline = {
528 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
529 autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
530 url: noop,
531 tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
532 link: /^!?\[(inside)\]\(href\)/,
533 reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
534 nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
535 strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
536 em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
537 code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
538 br: /^ {2,}\n(?!\s*$)/,
539 del: noop,
540 text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
541};
542
543inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
544inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
545
546inline.link = replace(inline.link)
547('inside', inline._inside)
548('href', inline._href)
549();
550
551inline.reflink = replace(inline.reflink)
552('inside', inline._inside)
553();
554
555/**
556 * Normal Inline Grammar
557 */
558
559inline.normal = merge({}, inline);
560
561/**
562 * Pedantic Inline Grammar
563 */
564
565inline.pedantic = merge({}, inline.normal, {
566 strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
567 em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
568});
569
570/**
571 * GFM Inline Grammar
572 */
573
574inline.gfm = merge({}, inline.normal, {
575 escape: replace(inline.escape)('])', '~|])')(),
576 url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
577 del: /^~~(?=\S)([\s\S]*?\S)~~/,
578 text: replace(inline.text)
579 (']|', '~]|')
580 ('|', '|https?://|')
581 ()
582});
583
584/**
585 * GFM + Line Breaks Inline Grammar
586 */
587
588inline.breaks = merge({}, inline.gfm, {
589 br: replace(inline.br)('{2,}', '*')(),
590 text: replace(inline.gfm.text)('{2,}', '*')()
591});
592
593/**
594 * Inline Lexer & Compiler
595 */
596
597function InlineLexer(links, annotextDoc, options) {
598 this.options = options || marked.defaults;
599 this.links = links;
600 this.rules = inline.normal;
601
602 this.isAnnotated = annotextDoc != null;
603 if (this.isAnnotated) {
604 this.annotextDoc = annotextDoc;
605 this.annotextDocParsed = new annotext().parse(annotextDoc, true);
606 }
607
608 if (!this.links) {
609 throw new
610 Error('Tokens array requires a `links` property.');
611 }
612
613 if (this.options.gfm) {
614 if (this.options.breaks) {
615 this.rules = inline.breaks;
616 } else {
617 this.rules = inline.gfm;
618 }
619 } else if (this.options.pedantic) {
620 this.rules = inline.pedantic;
621 }
622}
623
624/**
625 * Expose Inline Rules
626 */
627
628InlineLexer.rules = inline;
629
630/**
631 * Static Lexing/Compiling Method
632 */
633
634InlineLexer.output = function(src, links, options) {
635 var inline = new InlineLexer(links, this.annotextDoc, options);
636 return inline.output(src);
637};
638
639/**
640 * Lexing/Compiling
641 */
642
643InlineLexer.prototype.output = function(src, revisions) {
644 var out = '',
645 link, text, href, cap;
646
647 var att = function(cap) {
648 if (this.annotextDoc != null && cap.length > 0) {
649 return " " + createAttributionFromRevisions(revisions, 'inline');
650 } else
651 return "";
652 }
653
654 while (src) {
655 // escape
656 if (cap = this.rules.escape.exec(src)) {
657 src = src.substring(cap[0].length);
658 out += cap[1];
659 continue;
660 }
661
662 // autolink
663 if (cap = this.rules.autolink.exec(src)) {
664 src = src.substring(cap[0].length);
665 if (cap[2] === '@') {
666 text = cap[1].charAt(6) === ':' ? this.mangle(cap[1].substring(7)) : this.mangle(cap[1]);
667 href = this.mangle('mailto:') + text;
668 } else {
669 text = escape(cap[1]);
670 href = text;
671 }
672 var result = '<a href="' + href + '">' + text + '</a>';
673 if (this.options.escapeUrls) {
674 result = escape(result);
675 }
676 out += result;
677 continue;
678 }
679
680 // url (gfm)
681 if (cap = this.rules.url.exec(src)) {
682 src = src.substring(cap[0].length);
683 text = escape(cap[1]);
684 href = text;
685 var result = '<a href="' + href + '">' + text + '</a>';
686
687 if (this.options.escapeUrls) {
688 result = escape(result);
689 }
690 out += result;
691 continue;
692 }
693
694 // tag
695 if (cap = this.rules.tag.exec(src)) {
696 src = src.substring(cap[0].length);
697 out += this.options.sanitize ? escape(cap[0]) : cap[0];
698 continue;
699 }
700
701 // link
702 if (cap = this.rules.link.exec(src)) {
703 src = src.substring(cap[0].length);
704 out += this.outputLink(cap, {
705 href: cap[2],
706 title: cap[3]
707 });
708 continue;
709 }
710
711 // reflink, nolink
712 if ((cap = this.rules.reflink.exec(src)) || (cap = this.rules.nolink.exec(src))) {
713 src = src.substring(cap[0].length);
714 link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
715 link = this.links[link.toLowerCase()];
716 if (!link || !link.href) {
717 out += cap[0].charAt(0);
718 src = cap[0].substring(1) + src;
719 continue;
720 }
721 out += this.outputLink(cap, link);
722 continue;
723 }
724
725 // strong
726 if (cap = this.rules.strong.exec(src)) {
727 src = src.substring(cap[0].length);
728 out += '<strong>' + this.output(cap[2] || cap[1]) + '</strong>';
729 continue;
730 }
731
732 // em
733 if (cap = this.rules.em.exec(src)) {
734 src = src.substring(cap[0].length);
735 out += '<em>' + this.output(cap[2] || cap[1]) + '</em>';
736 continue;
737 }
738
739 // code
740 if (cap = this.rules.code.exec(src)) {
741 src = src.substring(cap[0].length);
742 out += '<code>' + escape(cap[2], true) + '</code>';
743 continue;
744 }
745
746 // br
747 if (cap = this.rules.br.exec(src)) {
748 src = src.substring(cap[0].length);
749 out += '<br>';
750 continue;
751 }
752
753 // del (gfm)
754 if (cap = this.rules.del.exec(src)) {
755 src = src.substring(cap[0].length);
756 out += '<del>' + this.output(cap[1]) + '</del>';
757 continue;
758 }
759
760 // text
761 if (cap = this.rules.text.exec(src)) {
762 src = src.substring(cap[0].length);
763 out += escape(this.smartypants(cap[0]));
764 continue;
765 }
766
767 if (src) {
768 throw new
769 Error('Infinite loop on byte: ' + src.charCodeAt(0));
770 }
771 }
772
773 return out;
774};
775
776/**
777 * Compile Link
778 */
779
780InlineLexer.prototype.outputLink = function(cap, link) {
781 var result = "";
782 if (cap[0].charAt(0) !== '!') {
783 result = '<a href="' + escape(link.href) + '"' + (link.title ? ' title="' + escape(link.title) + '"' : '') + '>' + this.output(cap[1]) + '</a>';
784 } else {
785 result = '<img src="' + escape(link.href) + '" alt="' + escape(cap[1]) + '"' + (link.title ? ' title="' + escape(link.title) + '"' : '') + '>';
786 }
787 if (this.options.escapeUrls) {
788 result = escape(result);
789 }
790 return result;
791};
792
793/**
794 * Smartypants Transformations
795 */
796
797InlineLexer.prototype.smartypants = function(text) {
798 if (!this.options.smartypants) return text;
799 return text
800 // em-dashes
801 .replace(/--/g, '\u2014')
802 // opening singles
803 .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
804 // closing singles & apostrophes
805 .replace(/'/g, '\u2019')
806 // opening doubles
807 .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
808 // closing doubles
809 .replace(/"/g, '\u201d')
810 // ellipses
811 .replace(/\.{3}/g, '\u2026');
812};
813
814/**
815 * Mangle Links
816 */
817
818InlineLexer.prototype.mangle = function(text) {
819 var out = '',
820 l = text.length,
821 i = 0,
822 ch;
823
824 for (; i < l; i++) {
825 ch = text.charCodeAt(i);
826 if (Math.random() > 0.5) {
827 ch = 'x' + ch.toString(16);
828 }
829 out += '&#' + ch + ';';
830 }
831
832 return out;
833};
834
835/**
836 * Parsing & Compiling
837 */
838
839function Parser(options, annotextDoc) {
840 this.tokens = [];
841 this.token = null;
842 this.options = options || marked.defaults;
843
844 this.isAnnotated = annotextDoc != null;
845 if (this.isAnnotated) {
846 this.annotextDoc = annotextDoc;
847 this.annotextDocParsed = new annotext().parse(annotextDoc, true);
848 }
849}
850
851/**
852 * Static Parse Method
853 */
854
855Parser.parse = function(src, annotextDoc, options) {
856 var parser = new Parser(options, annotextDoc);
857 return parser.parse(src);
858};
859
860/**
861 * Parse Loop
862 */
863
864Parser.prototype.parse = function(src) {
865 this.inline = new InlineLexer(src.links, this.annotextDoc, this.options);
866 this.tokens = src.reverse();
867
868 var out = '';
869 while (this.next()) {
870 out += this.tok();
871 }
872
873 return out;
874};
875
876/**
877 * Next Token
878 */
879
880Parser.prototype.next = function() {
881 return this.token = this.tokens.pop();
882};
883
884/**
885 * Preview Next Token
886 */
887
888Parser.prototype.peek = function() {
889 return this.tokens[this.tokens.length - 1] || 0;
890};
891
892/**
893 * Parse Text Tokens
894 */
895
896Parser.prototype.parseText = function() {
897 var body = this.token.text;
898
899 while (this.peek().type === 'text') {
900 body += '\n' + this.next().text;
901 }
902
903 return this.inline.output(body);
904};
905
906var arrayToCommaDelimited = function(list) {
907 var result = "";
908 for (var i = 0; i <= list.length - 1; i++) {
909 result += list[i];
910 if (i != list.length - 1)
911 result += ",";
912 }
913 return result;
914}
915
916
917var createAttributionFromRevisions = function(revisions, type) {
918 var attributeGroups = {};
919 revisions.forEach(function(revision) {
920 for (var attrKey in revision) {
921 if (attributeGroups[attrKey] == null)
922 attributeGroups[attrKey] = [];
923
924 if (attributeGroups[attrKey].indexOf(revision[attrKey]) == -1)
925 attributeGroups[attrKey].push(revision[attrKey]);
926 }
927 });
928
929 var attributions = [];
930 var excludeKeys = ['content', 'range_start', 'range_end', 'index'];
931
932 for (var attrKey in attributeGroups) {
933 if (excludeKeys.indexOf(attrKey) == -1) {
934 var attrTemp = 'data-annotext-' + attrKey +
935 '=\"' + arrayToCommaDelimited(attributeGroups[attrKey]) + "\"";
936 attributions.push(attrTemp);
937 }
938 }
939 attributions.sort();
940 attributions.reverse();
941 attributions.push('data-annotext-type=\"' + type + '\"');
942
943 var attribution = "";
944 for (var i = 0; i <= attributions.length - 1; i++) {
945 attribution += attributions[i];
946 if (i < attributions.length - 1)
947 attribution += " ";
948 }
949 return attribution;
950}
951
952/**
953 * Parse Current Token
954 */
955Parser.prototype.tok = function() {
956 var hasAttribution = this.token.revisions != undefined && this.token.revisions.length > 0;
957 var attribution = "";
958 if (hasAttribution) {
959 attribution = createAttributionFromRevisions(this.token.revisions, 'block');
960 }
961 var att = function() {
962 if (hasAttribution) {
963 return " " + attribution;
964 }
965 return "";
966 };
967
968
969 switch (this.token.type) {
970 case 'space':
971 {
972 return '';
973 }
974 case 'hr':
975 {
976 return '<hr' + att() + '>\n';
977 }
978 case 'heading':
979 {
980 return '<h' + this.token.depth + att() + ' class="' + this.token.text.toLowerCase().replace(/[^\w]+/g, '-') + '">' + this.inline.output(this.token.text) + '</h' + this.token.depth + '>\n';
981 }
982 case 'code':
983 {
984 if (this.options.highlight) {
985 var code = this.options.highlight(this.token.text, this.token.lang);
986 if (code != null && code !== this.token.text) {
987 this.token.escaped = true;
988 this.token.text = code;
989 }
990 }
991
992 if (!this.token.escaped) {
993 this.token.text = escape(this.token.text, true);
994 }
995
996 return '<pre' + att() + '><code' + att() + (this.token.lang ? ' class="' + this.options.langPrefix + this.token.lang + '"' : '') + '>' + this.token.text + '</code></pre>\n';
997 }
998 case 'table':
999 {
1000 var body = '',
1001 heading, i, row, cell, j;
1002
1003 // header
1004 body += '<thead' + att() + '>\n<tr' + att() + '>\n';
1005 for (i = 0; i < this.token.header.length; i++) {
1006 heading = this.inline.output(this.token.header[i]);
1007 body += '<th' + att();
1008 if (this.token.align[i]) {
1009 body += ' style="text-align:' + this.token.align[i] + '"';
1010 }
1011 body += '>' + heading + '</th>\n';
1012 }
1013 body += '</tr>\n</thead>\n';
1014
1015 // body
1016 body += '<tbody' + att() + '>\n'
1017 for (i = 0; i < this.token.cells.length; i++) {
1018 row = this.token.cells[i];
1019 body += '<tr' + att() + '>\n';
1020 for (j = 0; j < row.length; j++) {
1021 cell = this.inline.output(row[j]);
1022 body += '<td' + att();
1023 if (this.token.align[j]) {
1024 body += ' style="text-align:' + this.token.align[j] + '"';
1025 }
1026 body += '>' + cell + '</td>\n';
1027 }
1028 body += '</tr>\n';
1029 }
1030 body += '</tbody>\n';
1031
1032 return '<table' + att() + '>\n' + body + '</table>\n';
1033 }
1034 case 'blockquote_start':
1035 {
1036 var body = '';
1037
1038 while (this.next().type !== 'blockquote_end') {
1039 body += this.tok();
1040 }
1041
1042 return '<blockquote' + att() + '>\n' + body + '</blockquote>\n';
1043 }
1044 case 'list_start':
1045 {
1046 var type = this.token.ordered ? 'ol' : 'ul',
1047 body = '';
1048
1049 while (this.next().type !== 'list_end') {
1050 body += this.tok();
1051 }
1052
1053 return '<' + type + '' + att() + '>\n' + body + '</' + type + '>\n';
1054 }
1055 case 'list_item_start':
1056 {
1057 var body = '';
1058
1059 while (this.next().type !== 'list_item_end') {
1060 body += this.token.type === 'text' ? this.parseText() : this.tok();
1061 }
1062
1063 return '<li' + att() + '>' + body + '</li>\n';
1064 }
1065 case 'loose_item_start':
1066 {
1067 var body = '';
1068
1069 while (this.next().type !== 'list_item_end') {
1070 body += this.tok();
1071 }
1072
1073 return '<li' + att() + '>' + body + '</li>\n';
1074 }
1075 case 'html':
1076 {
1077 return !this.token.pre && !this.options.pedantic ? this.inline.output(this.token.text) : this.token.text;
1078 }
1079 case 'paragraph':
1080 {
1081 return '<p' + att() + '>' + this.inline.output(this.token.text) + '</p>\n';
1082 }
1083 case 'text':
1084 {
1085 return '<p' + att() + '>' + this.parseText() + '</p>\n';
1086 }
1087 }
1088};
1089
1090/**
1091 * Helpers
1092 */
1093
1094function escape(html, encode) {
1095 return html
1096 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
1097 .replace(/</g, '&lt;')
1098 .replace(/>/g, '&gt;')
1099 .replace(/"/g, '&quot;')
1100 .replace(/'/g, '&#39;');
1101}
1102
1103function replace(regex, opt) {
1104 regex = regex.source;
1105 opt = opt || '';
1106 return function self(name, val) {
1107 if (!name) return new RegExp(regex, opt);
1108 val = val.source || val;
1109 val = val.replace(/(^|[^\[])\^/g, '$1');
1110 regex = regex.replace(name, val);
1111 return self;
1112 };
1113}
1114
1115function noop() {}
1116noop.exec = noop;
1117
1118function merge(obj) {
1119 var i = 1,
1120 target, key;
1121
1122 for (; i < arguments.length; i++) {
1123 target = arguments[i];
1124 for (key in target) {
1125 if (Object.prototype.hasOwnProperty.call(target, key)) {
1126 obj[key] = target[key];
1127 }
1128 }
1129 }
1130
1131 return obj;
1132}
1133
1134/**
1135 * Marked
1136 */
1137
1138function marked(src, annotextDoc, opt, callback) {
1139 if (callback || typeof opt === 'function') {
1140 if (!callback) {
1141 callback = opt;
1142 opt = null;
1143 }
1144
1145 opt = merge({}, marked.defaults, opt || {});
1146
1147 var highlight = opt.highlight,
1148 tokens, pending, i = 0;
1149
1150 try {
1151 tokens = Lexer.lex(src, annotextDoc, opt)
1152 } catch (e) {
1153 return callback(e);
1154 }
1155
1156 pending = tokens.length;
1157
1158 var done = function() {
1159 var out, err;
1160
1161 try {
1162 out = Parser.parse(tokens, annotextDoc, opt);
1163 } catch (e) {
1164 err = e;
1165 }
1166
1167 opt.highlight = highlight;
1168
1169 return err ? callback(err) : callback(null, out);
1170 };
1171
1172 if (!highlight || highlight.length < 3) {
1173 return done();
1174 }
1175
1176 delete opt.highlight;
1177
1178 if (!pending) return done();
1179
1180 for (; i < tokens.length; i++) {
1181 (function(token) {
1182 if (token.type !== 'code') {
1183 return --pending || done();
1184 }
1185 return highlight(token.text, token.lang, function(err, code) {
1186 if (code == null || code === token.text) {
1187 return --pending || done();
1188 }
1189 token.text = code;
1190 token.escaped = true;
1191 --pending || done();
1192 });
1193 })(tokens[i]);
1194 }
1195
1196 return;
1197 }
1198 try {
1199 if (opt) opt = merge({}, marked.defaults, opt);
1200 return Parser.parse(Lexer.lex(src, annotextDoc, opt), annotextDoc, opt);
1201 } catch (e) {
1202 e.message += '\nPlease report this to https://github.com/chjj/marked.';
1203 if ((opt || marked.defaults).silent) {
1204 return '<p>An error occured:</p><pre>' + escape(e.message + '', true) + '</pre>';
1205 }
1206 throw e;
1207 }
1208}
1209
1210/**
1211 * Options
1212 */
1213
1214marked.options =
1215 marked.setOptions = function(opt) {
1216 merge(marked.defaults, opt);
1217 return marked;
1218};
1219
1220marked.defaults = {
1221 gfm: false,
1222 tables: true,
1223 breaks: false,
1224 pedantic: false,
1225 sanitize: true,
1226 smartLists: false,
1227 silent: false,
1228 highlight: null,
1229 langPrefix: 'lang-',
1230 smartypants: false,
1231 escapeUrls: true
1232};
1233
1234/**
1235 * Expose
1236 */
1237
1238marked.Parser = Parser;
1239marked.parser = Parser.parse;
1240
1241marked.Lexer = Lexer;
1242marked.lexer = Lexer.lex;
1243
1244marked.InlineLexer = InlineLexer;
1245marked.inlineLexer = InlineLexer.output;
1246
1247marked.parse = marked;
1248
1249module.exports = marked;