source: node_modules/remarkable/dist/esm/index.browser.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 121.8 KB
RevLine 
[d24f17c]1var textarea;
2
3function decodeEntity(name) {
4 textarea = textarea || document.createElement('textarea');
5 textarea.innerHTML = '&' + name + ';';
6 return textarea.value;
7}
8
9/**
10 * Utility functions
11 */
12
13function typeOf(obj) {
14 return Object.prototype.toString.call(obj);
15}
16
17function isString(obj) {
18 return typeOf(obj) === '[object String]';
19}
20
21var hasOwn = Object.prototype.hasOwnProperty;
22
23function has(object, key) {
24 return object
25 ? hasOwn.call(object, key)
26 : false;
27}
28
29// Extend objects
30//
31function assign(obj /*from1, from2, from3, ...*/) {
32 var sources = [].slice.call(arguments, 1);
33
34 sources.forEach(function (source) {
35 if (!source) { return; }
36
37 if (typeof source !== 'object') {
38 throw new TypeError(source + 'must be object');
39 }
40
41 Object.keys(source).forEach(function (key) {
42 obj[key] = source[key];
43 });
44 });
45
46 return obj;
47}
48
49////////////////////////////////////////////////////////////////////////////////
50
51var UNESCAPE_MD_RE = /\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
52
53function unescapeMd(str) {
54 if (str.indexOf('\\') < 0) { return str; }
55 return str.replace(UNESCAPE_MD_RE, '$1');
56}
57
58////////////////////////////////////////////////////////////////////////////////
59
60function isValidEntityCode(c) {
61 /*eslint no-bitwise:0*/
62 // broken sequence
63 if (c >= 0xD800 && c <= 0xDFFF) { return false; }
64 // never used
65 if (c >= 0xFDD0 && c <= 0xFDEF) { return false; }
66 if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; }
67 // control codes
68 if (c >= 0x00 && c <= 0x08) { return false; }
69 if (c === 0x0B) { return false; }
70 if (c >= 0x0E && c <= 0x1F) { return false; }
71 if (c >= 0x7F && c <= 0x9F) { return false; }
72 // out of range
73 if (c > 0x10FFFF) { return false; }
74 return true;
75}
76
77function fromCodePoint(c) {
78 /*eslint no-bitwise:0*/
79 if (c > 0xffff) {
80 c -= 0x10000;
81 var surrogate1 = 0xd800 + (c >> 10),
82 surrogate2 = 0xdc00 + (c & 0x3ff);
83
84 return String.fromCharCode(surrogate1, surrogate2);
85 }
86 return String.fromCharCode(c);
87}
88
89var NAMED_ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi;
90var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i;
91
92function replaceEntityPattern(match, name) {
93 var code = 0;
94 var decoded = decodeEntity(name);
95
96 if (name !== decoded) {
97 return decoded;
98 } else if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) {
99 code = name[1].toLowerCase() === 'x' ?
100 parseInt(name.slice(2), 16)
101 :
102 parseInt(name.slice(1), 10);
103 if (isValidEntityCode(code)) {
104 return fromCodePoint(code);
105 }
106 }
107 return match;
108}
109
110function replaceEntities(str) {
111 if (str.indexOf('&') < 0) { return str; }
112
113 return str.replace(NAMED_ENTITY_RE, replaceEntityPattern);
114}
115
116////////////////////////////////////////////////////////////////////////////////
117
118var HTML_ESCAPE_TEST_RE = /[&<>"]/;
119var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g;
120var HTML_REPLACEMENTS = {
121 '&': '&amp;',
122 '<': '&lt;',
123 '>': '&gt;',
124 '"': '&quot;'
125};
126
127function replaceUnsafeChar(ch) {
128 return HTML_REPLACEMENTS[ch];
129}
130
131function escapeHtml(str) {
132 if (HTML_ESCAPE_TEST_RE.test(str)) {
133 return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);
134 }
135 return str;
136}
137
138var utils = /*#__PURE__*/Object.freeze({
139 isString: isString,
140 has: has,
141 assign: assign,
142 unescapeMd: unescapeMd,
143 isValidEntityCode: isValidEntityCode,
144 fromCodePoint: fromCodePoint,
145 replaceEntities: replaceEntities,
146 escapeHtml: escapeHtml
147});
148
149/**
150 * Renderer rules cache
151 */
152
153var rules = {};
154
155/**
156 * Blockquotes
157 */
158
159rules.blockquote_open = function(/* tokens, idx, options, env */) {
160 return '<blockquote>\n';
161};
162
163rules.blockquote_close = function(tokens, idx /*, options, env */) {
164 return '</blockquote>' + getBreak(tokens, idx);
165};
166
167/**
168 * Code
169 */
170
171rules.code = function(tokens, idx /*, options, env */) {
172 if (tokens[idx].block) {
173 return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>' + getBreak(tokens, idx);
174 }
175 return '<code>' + escapeHtml(tokens[idx].content) + '</code>';
176};
177
178/**
179 * Fenced code blocks
180 */
181
182rules.fence = function(tokens, idx, options, env, instance) {
183 var token = tokens[idx];
184 var langClass = '';
185 var langPrefix = options.langPrefix;
186 var langName = '', fences, fenceName;
187 var highlighted;
188
189 if (token.params) {
190
191 //
192 // ```foo bar
193 //
194 // Try custom renderer "foo" first. That will simplify overwrite
195 // for diagrams, latex, and any other fenced block with custom look
196 //
197
198 fences = token.params.split(/\s+/g);
199 fenceName = fences.join(' ');
200
201 if (has(instance.rules.fence_custom, fences[0])) {
202 return instance.rules.fence_custom[fences[0]](tokens, idx, options, env, instance);
203 }
204
205 langName = escapeHtml(replaceEntities(unescapeMd(fenceName)));
206 langClass = ' class="' + langPrefix + langName + '"';
207 }
208
209 if (options.highlight) {
210 highlighted = options.highlight.apply(options.highlight, [ token.content ].concat(fences))
211 || escapeHtml(token.content);
212 } else {
213 highlighted = escapeHtml(token.content);
214 }
215
216 return '<pre><code' + langClass + '>'
217 + highlighted
218 + '</code></pre>'
219 + getBreak(tokens, idx);
220};
221
222rules.fence_custom = {};
223
224/**
225 * Headings
226 */
227
228rules.heading_open = function(tokens, idx /*, options, env */) {
229 return '<h' + tokens[idx].hLevel + '>';
230};
231rules.heading_close = function(tokens, idx /*, options, env */) {
232 return '</h' + tokens[idx].hLevel + '>\n';
233};
234
235/**
236 * Horizontal rules
237 */
238
239rules.hr = function(tokens, idx, options /*, env */) {
240 return (options.xhtmlOut ? '<hr />' : '<hr>') + getBreak(tokens, idx);
241};
242
243/**
244 * Bullets
245 */
246
247rules.bullet_list_open = function(/* tokens, idx, options, env */) {
248 return '<ul>\n';
249};
250rules.bullet_list_close = function(tokens, idx /*, options, env */) {
251 return '</ul>' + getBreak(tokens, idx);
252};
253
254/**
255 * List items
256 */
257
258rules.list_item_open = function(/* tokens, idx, options, env */) {
259 return '<li>';
260};
261rules.list_item_close = function(/* tokens, idx, options, env */) {
262 return '</li>\n';
263};
264
265/**
266 * Ordered list items
267 */
268
269rules.ordered_list_open = function(tokens, idx /*, options, env */) {
270 var token = tokens[idx];
271 var order = token.order > 1 ? ' start="' + token.order + '"' : '';
272 return '<ol' + order + '>\n';
273};
274rules.ordered_list_close = function(tokens, idx /*, options, env */) {
275 return '</ol>' + getBreak(tokens, idx);
276};
277
278/**
279 * Paragraphs
280 */
281
282rules.paragraph_open = function(tokens, idx /*, options, env */) {
283 return tokens[idx].tight ? '' : '<p>';
284};
285rules.paragraph_close = function(tokens, idx /*, options, env */) {
286 var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content);
287 return (tokens[idx].tight ? '' : '</p>') + (addBreak ? getBreak(tokens, idx) : '');
288};
289
290/**
291 * Links
292 */
293
294rules.link_open = function(tokens, idx, options /* env */) {
295 var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
296 var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : '';
297 return '<a href="' + escapeHtml(tokens[idx].href) + '"' + title + target + '>';
298};
299rules.link_close = function(/* tokens, idx, options, env */) {
300 return '</a>';
301};
302
303/**
304 * Images
305 */
306
307rules.image = function(tokens, idx, options /*, env */) {
308 var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
309 var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
310 var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(unescapeMd(tokens[idx].alt))) : '') + '"';
311 var suffix = options.xhtmlOut ? ' /' : '';
312 return '<img' + src + alt + title + suffix + '>';
313};
314
315/**
316 * Tables
317 */
318
319rules.table_open = function(/* tokens, idx, options, env */) {
320 return '<table>\n';
321};
322rules.table_close = function(/* tokens, idx, options, env */) {
323 return '</table>\n';
324};
325rules.thead_open = function(/* tokens, idx, options, env */) {
326 return '<thead>\n';
327};
328rules.thead_close = function(/* tokens, idx, options, env */) {
329 return '</thead>\n';
330};
331rules.tbody_open = function(/* tokens, idx, options, env */) {
332 return '<tbody>\n';
333};
334rules.tbody_close = function(/* tokens, idx, options, env */) {
335 return '</tbody>\n';
336};
337rules.tr_open = function(/* tokens, idx, options, env */) {
338 return '<tr>';
339};
340rules.tr_close = function(/* tokens, idx, options, env */) {
341 return '</tr>\n';
342};
343rules.th_open = function(tokens, idx /*, options, env */) {
344 var token = tokens[idx];
345 return '<th'
346 + (token.align ? ' style="text-align:' + token.align + '"' : '')
347 + '>';
348};
349rules.th_close = function(/* tokens, idx, options, env */) {
350 return '</th>';
351};
352rules.td_open = function(tokens, idx /*, options, env */) {
353 var token = tokens[idx];
354 return '<td'
355 + (token.align ? ' style="text-align:' + token.align + '"' : '')
356 + '>';
357};
358rules.td_close = function(/* tokens, idx, options, env */) {
359 return '</td>';
360};
361
362/**
363 * Bold
364 */
365
366rules.strong_open = function(/* tokens, idx, options, env */) {
367 return '<strong>';
368};
369rules.strong_close = function(/* tokens, idx, options, env */) {
370 return '</strong>';
371};
372
373/**
374 * Italicize
375 */
376
377rules.em_open = function(/* tokens, idx, options, env */) {
378 return '<em>';
379};
380rules.em_close = function(/* tokens, idx, options, env */) {
381 return '</em>';
382};
383
384/**
385 * Strikethrough
386 */
387
388rules.del_open = function(/* tokens, idx, options, env */) {
389 return '<del>';
390};
391rules.del_close = function(/* tokens, idx, options, env */) {
392 return '</del>';
393};
394
395/**
396 * Insert
397 */
398
399rules.ins_open = function(/* tokens, idx, options, env */) {
400 return '<ins>';
401};
402rules.ins_close = function(/* tokens, idx, options, env */) {
403 return '</ins>';
404};
405
406/**
407 * Highlight
408 */
409
410rules.mark_open = function(/* tokens, idx, options, env */) {
411 return '<mark>';
412};
413rules.mark_close = function(/* tokens, idx, options, env */) {
414 return '</mark>';
415};
416
417/**
418 * Super- and sub-script
419 */
420
421rules.sub = function(tokens, idx /*, options, env */) {
422 return '<sub>' + escapeHtml(tokens[idx].content) + '</sub>';
423};
424rules.sup = function(tokens, idx /*, options, env */) {
425 return '<sup>' + escapeHtml(tokens[idx].content) + '</sup>';
426};
427
428/**
429 * Breaks
430 */
431
432rules.hardbreak = function(tokens, idx, options /*, env */) {
433 return options.xhtmlOut ? '<br />\n' : '<br>\n';
434};
435rules.softbreak = function(tokens, idx, options /*, env */) {
436 return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n';
437};
438
439/**
440 * Text
441 */
442
443rules.text = function(tokens, idx /*, options, env */) {
444 return escapeHtml(tokens[idx].content);
445};
446
447/**
448 * Content
449 */
450
451rules.htmlblock = function(tokens, idx /*, options, env */) {
452 return tokens[idx].content;
453};
454rules.htmltag = function(tokens, idx /*, options, env */) {
455 return tokens[idx].content;
456};
457
458/**
459 * Abbreviations, initialism
460 */
461
462rules.abbr_open = function(tokens, idx /*, options, env */) {
463 return '<abbr title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '">';
464};
465rules.abbr_close = function(/* tokens, idx, options, env */) {
466 return '</abbr>';
467};
468
469/**
470 * Footnotes
471 */
472
473rules.footnote_ref = function(tokens, idx) {
474 var n = Number(tokens[idx].id + 1).toString();
475 var id = 'fnref' + n;
476 if (tokens[idx].subId > 0) {
477 id += ':' + tokens[idx].subId;
478 }
479 return '<sup class="footnote-ref"><a href="#fn' + n + '" id="' + id + '">[' + n + ']</a></sup>';
480};
481rules.footnote_block_open = function(tokens, idx, options) {
482 var hr = options.xhtmlOut
483 ? '<hr class="footnotes-sep" />\n'
484 : '<hr class="footnotes-sep">\n';
485 return hr + '<section class="footnotes">\n<ol class="footnotes-list">\n';
486};
487rules.footnote_block_close = function() {
488 return '</ol>\n</section>\n';
489};
490rules.footnote_open = function(tokens, idx) {
491 var id = Number(tokens[idx].id + 1).toString();
492 return '<li id="fn' + id + '" class="footnote-item">';
493};
494rules.footnote_close = function() {
495 return '</li>\n';
496};
497rules.footnote_anchor = function(tokens, idx) {
498 var n = Number(tokens[idx].id + 1).toString();
499 var id = 'fnref' + n;
500 if (tokens[idx].subId > 0) {
501 id += ':' + tokens[idx].subId;
502 }
503 return ' <a href="#' + id + '" class="footnote-backref">↩</a>';
504};
505
506/**
507 * Definition lists
508 */
509
510rules.dl_open = function() {
511 return '<dl>\n';
512};
513rules.dt_open = function() {
514 return '<dt>';
515};
516rules.dd_open = function() {
517 return '<dd>';
518};
519rules.dl_close = function() {
520 return '</dl>\n';
521};
522rules.dt_close = function() {
523 return '</dt>\n';
524};
525rules.dd_close = function() {
526 return '</dd>\n';
527};
528
529/**
530 * Helper functions
531 */
532
533function nextToken(tokens, idx) {
534 if (++idx >= tokens.length - 2) {
535 return idx;
536 }
537 if ((tokens[idx].type === 'paragraph_open' && tokens[idx].tight) &&
538 (tokens[idx + 1].type === 'inline' && tokens[idx + 1].content.length === 0) &&
539 (tokens[idx + 2].type === 'paragraph_close' && tokens[idx + 2].tight)) {
540 return nextToken(tokens, idx + 2);
541 }
542 return idx;
543}
544
545/**
546 * Check to see if `\n` is needed before the next token.
547 *
548 * @param {Array} `tokens`
549 * @param {Number} `idx`
550 * @return {String} Empty string or newline
551 * @api private
552 */
553
554var getBreak = rules.getBreak = function getBreak(tokens, idx) {
555 idx = nextToken(tokens, idx);
556 if (idx < tokens.length && tokens[idx].type === 'list_item_close') {
557 return '';
558 }
559 return '\n';
560};
561
562/**
563 * Renderer class. Renders HTML and exposes `rules` to allow
564 * local modifications.
565 */
566
567function Renderer() {
568 this.rules = assign({}, rules);
569
570 // exported helper, for custom rules only
571 this.getBreak = rules.getBreak;
572}
573
574/**
575 * Render a string of inline HTML with the given `tokens` and
576 * `options`.
577 *
578 * @param {Array} `tokens`
579 * @param {Object} `options`
580 * @param {Object} `env`
581 * @return {String}
582 * @api public
583 */
584
585Renderer.prototype.renderInline = function (tokens, options, env) {
586 var _rules = this.rules;
587 var len = tokens.length, i = 0;
588 var result = '';
589
590 while (len--) {
591 result += _rules[tokens[i].type](tokens, i++, options, env, this);
592 }
593
594 return result;
595};
596
597/**
598 * Render a string of HTML with the given `tokens` and
599 * `options`.
600 *
601 * @param {Array} `tokens`
602 * @param {Object} `options`
603 * @param {Object} `env`
604 * @return {String}
605 * @api public
606 */
607
608Renderer.prototype.render = function (tokens, options, env) {
609 var _rules = this.rules;
610 var len = tokens.length, i = -1;
611 var result = '';
612
613 while (++i < len) {
614 if (tokens[i].type === 'inline') {
615 result += this.renderInline(tokens[i].children, options, env);
616 } else {
617 result += _rules[tokens[i].type](tokens, i, options, env, this);
618 }
619 }
620 return result;
621};
622
623/**
624 * Ruler is a helper class for building responsibility chains from
625 * parse rules. It allows:
626 *
627 * - easy stack rules chains
628 * - getting main chain and named chains content (as arrays of functions)
629 *
630 * Helper methods, should not be used directly.
631 * @api private
632 */
633
634function Ruler() {
635 // List of added rules. Each element is:
636 //
637 // { name: XXX,
638 // enabled: Boolean,
639 // fn: Function(),
640 // alt: [ name2, name3 ] }
641 //
642 this.__rules__ = [];
643
644 // Cached rule chains.
645 //
646 // First level - chain name, '' for default.
647 // Second level - digital anchor for fast filtering by charcodes.
648 //
649 this.__cache__ = null;
650}
651
652/**
653 * Find the index of a rule by `name`.
654 *
655 * @param {String} `name`
656 * @return {Number} Index of the given `name`
657 * @api private
658 */
659
660Ruler.prototype.__find__ = function (name) {
661 var len = this.__rules__.length;
662 var i = -1;
663
664 while (len--) {
665 if (this.__rules__[++i].name === name) {
666 return i;
667 }
668 }
669 return -1;
670};
671
672/**
673 * Build the rules lookup cache
674 *
675 * @api private
676 */
677
678Ruler.prototype.__compile__ = function () {
679 var self = this;
680 var chains = [ '' ];
681
682 // collect unique names
683 self.__rules__.forEach(function (rule) {
684 if (!rule.enabled) {
685 return;
686 }
687
688 rule.alt.forEach(function (altName) {
689 if (chains.indexOf(altName) < 0) {
690 chains.push(altName);
691 }
692 });
693 });
694
695 self.__cache__ = {};
696
697 chains.forEach(function (chain) {
698 self.__cache__[chain] = [];
699 self.__rules__.forEach(function (rule) {
700 if (!rule.enabled) {
701 return;
702 }
703
704 if (chain && rule.alt.indexOf(chain) < 0) {
705 return;
706 }
707 self.__cache__[chain].push(rule.fn);
708 });
709 });
710};
711
712/**
713 * Ruler public methods
714 * ------------------------------------------------
715 */
716
717/**
718 * Replace rule function
719 *
720 * @param {String} `name` Rule name
721 * @param {Function `fn`
722 * @param {Object} `options`
723 * @api private
724 */
725
726Ruler.prototype.at = function (name, fn, options) {
727 var idx = this.__find__(name);
728 var opt = options || {};
729
730 if (idx === -1) {
731 throw new Error('Parser rule not found: ' + name);
732 }
733
734 this.__rules__[idx].fn = fn;
735 this.__rules__[idx].alt = opt.alt || [];
736 this.__cache__ = null;
737};
738
739/**
740 * Add a rule to the chain before given the `ruleName`.
741 *
742 * @param {String} `beforeName`
743 * @param {String} `ruleName`
744 * @param {Function} `fn`
745 * @param {Object} `options`
746 * @api private
747 */
748
749Ruler.prototype.before = function (beforeName, ruleName, fn, options) {
750 var idx = this.__find__(beforeName);
751 var opt = options || {};
752
753 if (idx === -1) {
754 throw new Error('Parser rule not found: ' + beforeName);
755 }
756
757 this.__rules__.splice(idx, 0, {
758 name: ruleName,
759 enabled: true,
760 fn: fn,
761 alt: opt.alt || []
762 });
763
764 this.__cache__ = null;
765};
766
767/**
768 * Add a rule to the chain after the given `ruleName`.
769 *
770 * @param {String} `afterName`
771 * @param {String} `ruleName`
772 * @param {Function} `fn`
773 * @param {Object} `options`
774 * @api private
775 */
776
777Ruler.prototype.after = function (afterName, ruleName, fn, options) {
778 var idx = this.__find__(afterName);
779 var opt = options || {};
780
781 if (idx === -1) {
782 throw new Error('Parser rule not found: ' + afterName);
783 }
784
785 this.__rules__.splice(idx + 1, 0, {
786 name: ruleName,
787 enabled: true,
788 fn: fn,
789 alt: opt.alt || []
790 });
791
792 this.__cache__ = null;
793};
794
795/**
796 * Add a rule to the end of chain.
797 *
798 * @param {String} `ruleName`
799 * @param {Function} `fn`
800 * @param {Object} `options`
801 * @return {String}
802 */
803
804Ruler.prototype.push = function (ruleName, fn, options) {
805 var opt = options || {};
806
807 this.__rules__.push({
808 name: ruleName,
809 enabled: true,
810 fn: fn,
811 alt: opt.alt || []
812 });
813
814 this.__cache__ = null;
815};
816
817/**
818 * Enable a rule or list of rules.
819 *
820 * @param {String|Array} `list` Name or array of rule names to enable
821 * @param {Boolean} `strict` If `true`, all non listed rules will be disabled.
822 * @api private
823 */
824
825Ruler.prototype.enable = function (list, strict) {
826 list = !Array.isArray(list)
827 ? [ list ]
828 : list;
829
830 // In strict mode disable all existing rules first
831 if (strict) {
832 this.__rules__.forEach(function (rule) {
833 rule.enabled = false;
834 });
835 }
836
837 // Search by name and enable
838 list.forEach(function (name) {
839 var idx = this.__find__(name);
840 if (idx < 0) {
841 throw new Error('Rules manager: invalid rule name ' + name);
842 }
843 this.__rules__[idx].enabled = true;
844 }, this);
845
846 this.__cache__ = null;
847};
848
849
850/**
851 * Disable a rule or list of rules.
852 *
853 * @param {String|Array} `list` Name or array of rule names to disable
854 * @api private
855 */
856
857Ruler.prototype.disable = function (list) {
858 list = !Array.isArray(list)
859 ? [ list ]
860 : list;
861
862 // Search by name and disable
863 list.forEach(function (name) {
864 var idx = this.__find__(name);
865 if (idx < 0) {
866 throw new Error('Rules manager: invalid rule name ' + name);
867 }
868 this.__rules__[idx].enabled = false;
869 }, this);
870
871 this.__cache__ = null;
872};
873
874/**
875 * Get a rules list as an array of functions.
876 *
877 * @param {String} `chainName`
878 * @return {Object}
879 * @api private
880 */
881
882Ruler.prototype.getRules = function (chainName) {
883 if (this.__cache__ === null) {
884 this.__compile__();
885 }
886 return this.__cache__[chainName] || [];
887};
888
889function block(state) {
890
891 if (state.inlineMode) {
892 state.tokens.push({
893 type: 'inline',
894 content: state.src.replace(/\n/g, ' ').trim(),
895 level: 0,
896 lines: [ 0, 1 ],
897 children: []
898 });
899
900 } else {
901 state.block.parse(state.src, state.options, state.env, state.tokens);
902 }
903}
904
905// Inline parser state
906
907function StateInline(src, parserInline, options, env, outTokens) {
908 this.src = src;
909 this.env = env;
910 this.options = options;
911 this.parser = parserInline;
912 this.tokens = outTokens;
913 this.pos = 0;
914 this.posMax = this.src.length;
915 this.level = 0;
916 this.pending = '';
917 this.pendingLevel = 0;
918
919 this.cache = []; // Stores { start: end } pairs. Useful for backtrack
920 // optimization of pairs parse (emphasis, strikes).
921
922 // Link parser state vars
923
924 this.isInLabel = false; // Set true when seek link label - we should disable
925 // "paired" rules (emphasis, strikes) to not skip
926 // tailing `]`
927
928 this.linkLevel = 0; // Increment for each nesting link. Used to prevent
929 // nesting in definitions
930
931 this.linkContent = ''; // Temporary storage for link url
932
933 this.labelUnmatchedScopes = 0; // Track unpaired `[` for link labels
934 // (backtrack optimization)
935}
936
937// Flush pending text
938//
939StateInline.prototype.pushPending = function () {
940 this.tokens.push({
941 type: 'text',
942 content: this.pending,
943 level: this.pendingLevel
944 });
945 this.pending = '';
946};
947
948// Push new token to "stream".
949// If pending text exists - flush it as text token
950//
951StateInline.prototype.push = function (token) {
952 if (this.pending) {
953 this.pushPending();
954 }
955
956 this.tokens.push(token);
957 this.pendingLevel = this.level;
958};
959
960// Store value to cache.
961// !!! Implementation has parser-specific optimizations
962// !!! keys MUST be integer, >= 0; values MUST be integer, > 0
963//
964StateInline.prototype.cacheSet = function (key, val) {
965 for (var i = this.cache.length; i <= key; i++) {
966 this.cache.push(0);
967 }
968
969 this.cache[key] = val;
970};
971
972// Get cache value
973//
974StateInline.prototype.cacheGet = function (key) {
975 return key < this.cache.length ? this.cache[key] : 0;
976};
977
978/**
979 * Parse link labels
980 *
981 * This function assumes that first character (`[`) already matches;
982 * returns the end of the label.
983 *
984 * @param {Object} state
985 * @param {Number} start
986 * @api private
987 */
988
989function parseLinkLabel(state, start) {
990 var level, found, marker,
991 labelEnd = -1,
992 max = state.posMax,
993 oldPos = state.pos,
994 oldFlag = state.isInLabel;
995
996 if (state.isInLabel) { return -1; }
997
998 if (state.labelUnmatchedScopes) {
999 state.labelUnmatchedScopes--;
1000 return -1;
1001 }
1002
1003 state.pos = start + 1;
1004 state.isInLabel = true;
1005 level = 1;
1006
1007 while (state.pos < max) {
1008 marker = state.src.charCodeAt(state.pos);
1009 if (marker === 0x5B /* [ */) {
1010 level++;
1011 } else if (marker === 0x5D /* ] */) {
1012 level--;
1013 if (level === 0) {
1014 found = true;
1015 break;
1016 }
1017 }
1018
1019 state.parser.skipToken(state);
1020 }
1021
1022 if (found) {
1023 labelEnd = state.pos;
1024 state.labelUnmatchedScopes = 0;
1025 } else {
1026 state.labelUnmatchedScopes = level - 1;
1027 }
1028
1029 // restore old state
1030 state.pos = oldPos;
1031 state.isInLabel = oldFlag;
1032
1033 return labelEnd;
1034}
1035
1036// Parse abbreviation definitions, i.e. `*[abbr]: description`
1037
1038
1039function parseAbbr(str, parserInline, options, env) {
1040 var state, labelEnd, pos, max, label, title;
1041
1042 if (str.charCodeAt(0) !== 0x2A/* * */) { return -1; }
1043 if (str.charCodeAt(1) !== 0x5B/* [ */) { return -1; }
1044
1045 if (str.indexOf(']:') === -1) { return -1; }
1046
1047 state = new StateInline(str, parserInline, options, env, []);
1048 labelEnd = parseLinkLabel(state, 1);
1049
1050 if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; }
1051
1052 max = state.posMax;
1053
1054 // abbr title is always one line, so looking for ending "\n" here
1055 for (pos = labelEnd + 2; pos < max; pos++) {
1056 if (state.src.charCodeAt(pos) === 0x0A) { break; }
1057 }
1058
1059 label = str.slice(2, labelEnd);
1060 title = str.slice(labelEnd + 2, pos).trim();
1061 if (title.length === 0) { return -1; }
1062 if (!env.abbreviations) { env.abbreviations = {}; }
1063 // prepend ':' to avoid conflict with Object.prototype members
1064 if (typeof env.abbreviations[':' + label] === 'undefined') {
1065 env.abbreviations[':' + label] = title;
1066 }
1067
1068 return pos;
1069}
1070
1071function abbr(state) {
1072 var tokens = state.tokens, i, l, content, pos;
1073
1074 if (state.inlineMode) {
1075 return;
1076 }
1077
1078 // Parse inlines
1079 for (i = 1, l = tokens.length - 1; i < l; i++) {
1080 if (tokens[i - 1].type === 'paragraph_open' &&
1081 tokens[i].type === 'inline' &&
1082 tokens[i + 1].type === 'paragraph_close') {
1083
1084 content = tokens[i].content;
1085 while (content.length) {
1086 pos = parseAbbr(content, state.inline, state.options, state.env);
1087 if (pos < 0) { break; }
1088 content = content.slice(pos).trim();
1089 }
1090
1091 tokens[i].content = content;
1092 if (!content.length) {
1093 tokens[i - 1].tight = true;
1094 tokens[i + 1].tight = true;
1095 }
1096 }
1097 }
1098}
1099
1100function normalizeLink(url) {
1101 var normalized = replaceEntities(url);
1102 // We shouldn't care about the result of malformed URIs,
1103 // and should not throw an exception.
1104 try {
1105 normalized = decodeURI(normalized);
1106 } catch (err) {}
1107 return encodeURI(normalized);
1108}
1109
1110/**
1111 * Parse link destination
1112 *
1113 * - on success it returns a string and updates state.pos;
1114 * - on failure it returns null
1115 *
1116 * @param {Object} state
1117 * @param {Number} pos
1118 * @api private
1119 */
1120
1121function parseLinkDestination(state, pos) {
1122 var code, level, link,
1123 start = pos,
1124 max = state.posMax;
1125
1126 if (state.src.charCodeAt(pos) === 0x3C /* < */) {
1127 pos++;
1128 while (pos < max) {
1129 code = state.src.charCodeAt(pos);
1130 if (code === 0x0A /* \n */) { return false; }
1131 if (code === 0x3E /* > */) {
1132 link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos)));
1133 if (!state.parser.validateLink(link)) { return false; }
1134 state.pos = pos + 1;
1135 state.linkContent = link;
1136 return true;
1137 }
1138 if (code === 0x5C /* \ */ && pos + 1 < max) {
1139 pos += 2;
1140 continue;
1141 }
1142
1143 pos++;
1144 }
1145
1146 // no closing '>'
1147 return false;
1148 }
1149
1150 // this should be ... } else { ... branch
1151
1152 level = 0;
1153 while (pos < max) {
1154 code = state.src.charCodeAt(pos);
1155
1156 if (code === 0x20) { break; }
1157
1158 // ascii control chars
1159 if (code < 0x20 || code === 0x7F) { break; }
1160
1161 if (code === 0x5C /* \ */ && pos + 1 < max) {
1162 pos += 2;
1163 continue;
1164 }
1165
1166 if (code === 0x28 /* ( */) {
1167 level++;
1168 if (level > 1) { break; }
1169 }
1170
1171 if (code === 0x29 /* ) */) {
1172 level--;
1173 if (level < 0) { break; }
1174 }
1175
1176 pos++;
1177 }
1178
1179 if (start === pos) { return false; }
1180
1181 link = unescapeMd(state.src.slice(start, pos));
1182 if (!state.parser.validateLink(link)) { return false; }
1183
1184 state.linkContent = link;
1185 state.pos = pos;
1186 return true;
1187}
1188
1189/**
1190 * Parse link title
1191 *
1192 * - on success it returns a string and updates state.pos;
1193 * - on failure it returns null
1194 *
1195 * @param {Object} state
1196 * @param {Number} pos
1197 * @api private
1198 */
1199
1200function parseLinkTitle(state, pos) {
1201 var code,
1202 start = pos,
1203 max = state.posMax,
1204 marker = state.src.charCodeAt(pos);
1205
1206 if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return false; }
1207
1208 pos++;
1209
1210 // if opening marker is "(", switch it to closing marker ")"
1211 if (marker === 0x28) { marker = 0x29; }
1212
1213 while (pos < max) {
1214 code = state.src.charCodeAt(pos);
1215 if (code === marker) {
1216 state.pos = pos + 1;
1217 state.linkContent = unescapeMd(state.src.slice(start + 1, pos));
1218 return true;
1219 }
1220 if (code === 0x5C /* \ */ && pos + 1 < max) {
1221 pos += 2;
1222 continue;
1223 }
1224
1225 pos++;
1226 }
1227
1228 return false;
1229}
1230
1231function normalizeReference(str) {
1232 // use .toUpperCase() instead of .toLowerCase()
1233 // here to avoid a conflict with Object.prototype
1234 // members (most notably, `__proto__`)
1235 return str.trim().replace(/\s+/g, ' ').toUpperCase();
1236}
1237
1238function parseReference(str, parser, options, env) {
1239 var state, labelEnd, pos, max, code, start, href, title, label;
1240
1241 if (str.charCodeAt(0) !== 0x5B/* [ */) { return -1; }
1242
1243 if (str.indexOf(']:') === -1) { return -1; }
1244
1245 state = new StateInline(str, parser, options, env, []);
1246 labelEnd = parseLinkLabel(state, 0);
1247
1248 if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; }
1249
1250 max = state.posMax;
1251
1252 // [label]: destination 'title'
1253 // ^^^ skip optional whitespace here
1254 for (pos = labelEnd + 2; pos < max; pos++) {
1255 code = state.src.charCodeAt(pos);
1256 if (code !== 0x20 && code !== 0x0A) { break; }
1257 }
1258
1259 // [label]: destination 'title'
1260 // ^^^^^^^^^^^ parse this
1261 if (!parseLinkDestination(state, pos)) { return -1; }
1262 href = state.linkContent;
1263 pos = state.pos;
1264
1265 // [label]: destination 'title'
1266 // ^^^ skipping those spaces
1267 start = pos;
1268 for (pos = pos + 1; pos < max; pos++) {
1269 code = state.src.charCodeAt(pos);
1270 if (code !== 0x20 && code !== 0x0A) { break; }
1271 }
1272
1273 // [label]: destination 'title'
1274 // ^^^^^^^ parse this
1275 if (pos < max && start !== pos && parseLinkTitle(state, pos)) {
1276 title = state.linkContent;
1277 pos = state.pos;
1278 } else {
1279 title = '';
1280 pos = start;
1281 }
1282
1283 // ensure that the end of the line is empty
1284 while (pos < max && state.src.charCodeAt(pos) === 0x20/* space */) { pos++; }
1285 if (pos < max && state.src.charCodeAt(pos) !== 0x0A) { return -1; }
1286
1287 label = normalizeReference(str.slice(1, labelEnd));
1288 if (typeof env.references[label] === 'undefined') {
1289 env.references[label] = { title: title, href: href };
1290 }
1291
1292 return pos;
1293}
1294
1295
1296function references(state) {
1297 var tokens = state.tokens, i, l, content, pos;
1298
1299 state.env.references = state.env.references || {};
1300
1301 if (state.inlineMode) {
1302 return;
1303 }
1304
1305 // Scan definitions in paragraph inlines
1306 for (i = 1, l = tokens.length - 1; i < l; i++) {
1307 if (tokens[i].type === 'inline' &&
1308 tokens[i - 1].type === 'paragraph_open' &&
1309 tokens[i + 1].type === 'paragraph_close') {
1310
1311 content = tokens[i].content;
1312 while (content.length) {
1313 pos = parseReference(content, state.inline, state.options, state.env);
1314 if (pos < 0) { break; }
1315 content = content.slice(pos).trim();
1316 }
1317
1318 tokens[i].content = content;
1319 if (!content.length) {
1320 tokens[i - 1].tight = true;
1321 tokens[i + 1].tight = true;
1322 }
1323 }
1324 }
1325}
1326
1327function inline(state) {
1328 var tokens = state.tokens, tok, i, l;
1329
1330 // Parse inlines
1331 for (i = 0, l = tokens.length; i < l; i++) {
1332 tok = tokens[i];
1333 if (tok.type === 'inline') {
1334 state.inline.parse(tok.content, state.options, state.env, tok.children);
1335 }
1336 }
1337}
1338
1339function footnote_block(state) {
1340 var i, l, j, t, lastParagraph, list, tokens, current, currentLabel,
1341 level = 0,
1342 insideRef = false,
1343 refTokens = {};
1344
1345 if (!state.env.footnotes) { return; }
1346
1347 state.tokens = state.tokens.filter(function(tok) {
1348 if (tok.type === 'footnote_reference_open') {
1349 insideRef = true;
1350 current = [];
1351 currentLabel = tok.label;
1352 return false;
1353 }
1354 if (tok.type === 'footnote_reference_close') {
1355 insideRef = false;
1356 // prepend ':' to avoid conflict with Object.prototype members
1357 refTokens[':' + currentLabel] = current;
1358 return false;
1359 }
1360 if (insideRef) { current.push(tok); }
1361 return !insideRef;
1362 });
1363
1364 if (!state.env.footnotes.list) { return; }
1365 list = state.env.footnotes.list;
1366
1367 state.tokens.push({
1368 type: 'footnote_block_open',
1369 level: level++
1370 });
1371 for (i = 0, l = list.length; i < l; i++) {
1372 state.tokens.push({
1373 type: 'footnote_open',
1374 id: i,
1375 level: level++
1376 });
1377
1378 if (list[i].tokens) {
1379 tokens = [];
1380 tokens.push({
1381 type: 'paragraph_open',
1382 tight: false,
1383 level: level++
1384 });
1385 tokens.push({
1386 type: 'inline',
1387 content: '',
1388 level: level,
1389 children: list[i].tokens
1390 });
1391 tokens.push({
1392 type: 'paragraph_close',
1393 tight: false,
1394 level: --level
1395 });
1396 } else if (list[i].label) {
1397 tokens = refTokens[':' + list[i].label];
1398 }
1399
1400 state.tokens = state.tokens.concat(tokens);
1401 if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') {
1402 lastParagraph = state.tokens.pop();
1403 } else {
1404 lastParagraph = null;
1405 }
1406
1407 t = list[i].count > 0 ? list[i].count : 1;
1408 for (j = 0; j < t; j++) {
1409 state.tokens.push({
1410 type: 'footnote_anchor',
1411 id: i,
1412 subId: j,
1413 level: level
1414 });
1415 }
1416
1417 if (lastParagraph) {
1418 state.tokens.push(lastParagraph);
1419 }
1420
1421 state.tokens.push({
1422 type: 'footnote_close',
1423 level: --level
1424 });
1425 }
1426 state.tokens.push({
1427 type: 'footnote_block_close',
1428 level: --level
1429 });
1430}
1431
1432// Enclose abbreviations in <abbr> tags
1433//
1434
1435var PUNCT_CHARS = ' \n()[]\'".,!?-';
1436
1437
1438// from Google closure library
1439// http://closure-library.googlecode.com/git-history/docs/local_closure_goog_string_string.js.source.html#line1021
1440function regEscape(s) {
1441 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1');
1442}
1443
1444
1445function abbr2(state) {
1446 var i, j, l, tokens, token, text, nodes, pos, level, reg, m, regText,
1447 blockTokens = state.tokens;
1448
1449 if (!state.env.abbreviations) { return; }
1450 if (!state.env.abbrRegExp) {
1451 regText = '(^|[' + PUNCT_CHARS.split('').map(regEscape).join('') + '])'
1452 + '(' + Object.keys(state.env.abbreviations).map(function (x) {
1453 return x.substr(1);
1454 }).sort(function (a, b) {
1455 return b.length - a.length;
1456 }).map(regEscape).join('|') + ')'
1457 + '($|[' + PUNCT_CHARS.split('').map(regEscape).join('') + '])';
1458 state.env.abbrRegExp = new RegExp(regText, 'g');
1459 }
1460 reg = state.env.abbrRegExp;
1461
1462 for (j = 0, l = blockTokens.length; j < l; j++) {
1463 if (blockTokens[j].type !== 'inline') { continue; }
1464 tokens = blockTokens[j].children;
1465
1466 // We scan from the end, to keep position when new tags added.
1467 for (i = tokens.length - 1; i >= 0; i--) {
1468 token = tokens[i];
1469 if (token.type !== 'text') { continue; }
1470
1471 pos = 0;
1472 text = token.content;
1473 reg.lastIndex = 0;
1474 level = token.level;
1475 nodes = [];
1476
1477 while ((m = reg.exec(text))) {
1478 if (reg.lastIndex > pos) {
1479 nodes.push({
1480 type: 'text',
1481 content: text.slice(pos, m.index + m[1].length),
1482 level: level
1483 });
1484 }
1485
1486 nodes.push({
1487 type: 'abbr_open',
1488 title: state.env.abbreviations[':' + m[2]],
1489 level: level++
1490 });
1491 nodes.push({
1492 type: 'text',
1493 content: m[2],
1494 level: level
1495 });
1496 nodes.push({
1497 type: 'abbr_close',
1498 level: --level
1499 });
1500 pos = reg.lastIndex - m[3].length;
1501 }
1502
1503 if (!nodes.length) { continue; }
1504
1505 if (pos < text.length) {
1506 nodes.push({
1507 type: 'text',
1508 content: text.slice(pos),
1509 level: level
1510 });
1511 }
1512
1513 // replace current node
1514 blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1));
1515 }
1516 }
1517}
1518
1519// Simple typographical replacements
1520//
1521// TODO:
1522// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
1523// - miltiplication 2 x 4 -> 2 × 4
1524
1525var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/;
1526
1527var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig;
1528var SCOPED_ABBR = {
1529 'c': '©',
1530 'r': '®',
1531 'p': '§',
1532 'tm': '™'
1533};
1534
1535function replaceScopedAbbr(str) {
1536 if (str.indexOf('(') < 0) { return str; }
1537
1538 return str.replace(SCOPED_ABBR_RE, function(match, name) {
1539 return SCOPED_ABBR[name.toLowerCase()];
1540 });
1541}
1542
1543
1544function replace(state) {
1545 var i, token, text, inlineTokens, blkIdx;
1546
1547 if (!state.options.typographer) { return; }
1548
1549 for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
1550
1551 if (state.tokens[blkIdx].type !== 'inline') { continue; }
1552
1553 inlineTokens = state.tokens[blkIdx].children;
1554
1555 for (i = inlineTokens.length - 1; i >= 0; i--) {
1556 token = inlineTokens[i];
1557 if (token.type === 'text') {
1558 text = token.content;
1559
1560 text = replaceScopedAbbr(text);
1561
1562 if (RARE_RE.test(text)) {
1563 text = text
1564 .replace(/\+-/g, '±')
1565 // .., ..., ....... -> …
1566 // but ?..... & !..... -> ?.. & !..
1567 .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..')
1568 .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',')
1569 // em-dash
1570 .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2')
1571 // en-dash
1572 .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2')
1573 .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2');
1574 }
1575
1576 token.content = text;
1577 }
1578 }
1579 }
1580}
1581
1582// Convert straight quotation marks to typographic ones
1583//
1584
1585var QUOTE_TEST_RE = /['"]/;
1586var QUOTE_RE = /['"]/g;
1587var PUNCT_RE = /[-\s()\[\]]/;
1588var APOSTROPHE = '’';
1589
1590// This function returns true if the character at `pos`
1591// could be inside a word.
1592function isLetter(str, pos) {
1593 if (pos < 0 || pos >= str.length) { return false; }
1594 return !PUNCT_RE.test(str[pos]);
1595}
1596
1597
1598function replaceAt(str, index, ch) {
1599 return str.substr(0, index) + ch + str.substr(index + 1);
1600}
1601
1602
1603function smartquotes(state) {
1604 /*eslint max-depth:0*/
1605 var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item,
1606 canOpen, canClose, j, isSingle, blkIdx, tokens,
1607 stack;
1608
1609 if (!state.options.typographer) { return; }
1610
1611 stack = [];
1612
1613 for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
1614
1615 if (state.tokens[blkIdx].type !== 'inline') { continue; }
1616
1617 tokens = state.tokens[blkIdx].children;
1618 stack.length = 0;
1619
1620 for (i = 0; i < tokens.length; i++) {
1621 token = tokens[i];
1622
1623 if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; }
1624
1625 thisLevel = tokens[i].level;
1626
1627 for (j = stack.length - 1; j >= 0; j--) {
1628 if (stack[j].level <= thisLevel) { break; }
1629 }
1630 stack.length = j + 1;
1631
1632 text = token.content;
1633 pos = 0;
1634 max = text.length;
1635
1636 /*eslint no-labels:0,block-scoped-var:0*/
1637 OUTER:
1638 while (pos < max) {
1639 QUOTE_RE.lastIndex = pos;
1640 t = QUOTE_RE.exec(text);
1641 if (!t) { break; }
1642
1643 lastSpace = !isLetter(text, t.index - 1);
1644 pos = t.index + 1;
1645 isSingle = (t[0] === "'");
1646 nextSpace = !isLetter(text, pos);
1647
1648 if (!nextSpace && !lastSpace) {
1649 // middle of word
1650 if (isSingle) {
1651 token.content = replaceAt(token.content, t.index, APOSTROPHE);
1652 }
1653 continue;
1654 }
1655
1656 canOpen = !nextSpace;
1657 canClose = !lastSpace;
1658
1659 if (canClose) {
1660 // this could be a closing quote, rewind the stack to get a match
1661 for (j = stack.length - 1; j >= 0; j--) {
1662 item = stack[j];
1663 if (stack[j].level < thisLevel) { break; }
1664 if (item.single === isSingle && stack[j].level === thisLevel) {
1665 item = stack[j];
1666 if (isSingle) {
1667 tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[2]);
1668 token.content = replaceAt(token.content, t.index, state.options.quotes[3]);
1669 } else {
1670 tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[0]);
1671 token.content = replaceAt(token.content, t.index, state.options.quotes[1]);
1672 }
1673 stack.length = j;
1674 continue OUTER;
1675 }
1676 }
1677 }
1678
1679 if (canOpen) {
1680 stack.push({
1681 token: i,
1682 pos: t.index,
1683 single: isSingle,
1684 level: thisLevel
1685 });
1686 } else if (canClose && isSingle) {
1687 token.content = replaceAt(token.content, t.index, APOSTROPHE);
1688 }
1689 }
1690 }
1691 }
1692}
1693
1694/**
1695 * Core parser `rules`
1696 */
1697
1698var _rules = [
1699 [ 'block', block ],
1700 [ 'abbr', abbr ],
1701 [ 'references', references ],
1702 [ 'inline', inline ],
1703 [ 'footnote_tail', footnote_block ],
1704 [ 'abbr2', abbr2 ],
1705 [ 'replacements', replace ],
1706 [ 'smartquotes', smartquotes ],
1707];
1708
1709/**
1710 * Class for top level (`core`) parser rules
1711 *
1712 * @api private
1713 */
1714
1715function Core() {
1716 this.options = {};
1717 this.ruler = new Ruler();
1718 for (var i = 0; i < _rules.length; i++) {
1719 this.ruler.push(_rules[i][0], _rules[i][1]);
1720 }
1721}
1722
1723/**
1724 * Process rules with the given `state`
1725 *
1726 * @param {Object} `state`
1727 * @api private
1728 */
1729
1730Core.prototype.process = function (state) {
1731 var i, l, rules;
1732 rules = this.ruler.getRules('');
1733 for (i = 0, l = rules.length; i < l; i++) {
1734 rules[i](state);
1735 }
1736};
1737
1738// Parser state class
1739
1740function StateBlock(src, parser, options, env, tokens) {
1741 var ch, s, start, pos, len, indent, indent_found;
1742
1743 this.src = src;
1744
1745 // Shortcuts to simplify nested calls
1746 this.parser = parser;
1747
1748 this.options = options;
1749
1750 this.env = env;
1751
1752 //
1753 // Internal state vartiables
1754 //
1755
1756 this.tokens = tokens;
1757
1758 this.bMarks = []; // line begin offsets for fast jumps
1759 this.eMarks = []; // line end offsets for fast jumps
1760 this.tShift = []; // indent for each line
1761
1762 // block parser variables
1763 this.blkIndent = 0; // required block content indent
1764 // (for example, if we are in list)
1765 this.line = 0; // line index in src
1766 this.lineMax = 0; // lines count
1767 this.tight = false; // loose/tight mode for lists
1768 this.parentType = 'root'; // if `list`, block parser stops on two newlines
1769 this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any)
1770
1771 this.level = 0;
1772
1773 // renderer
1774 this.result = '';
1775
1776 // Create caches
1777 // Generate markers.
1778 s = this.src;
1779 indent = 0;
1780 indent_found = false;
1781
1782 for (start = pos = indent = 0, len = s.length; pos < len; pos++) {
1783 ch = s.charCodeAt(pos);
1784
1785 if (!indent_found) {
1786 if (ch === 0x20/* space */) {
1787 indent++;
1788 continue;
1789 } else {
1790 indent_found = true;
1791 }
1792 }
1793
1794 if (ch === 0x0A || pos === len - 1) {
1795 if (ch !== 0x0A) { pos++; }
1796 this.bMarks.push(start);
1797 this.eMarks.push(pos);
1798 this.tShift.push(indent);
1799
1800 indent_found = false;
1801 indent = 0;
1802 start = pos + 1;
1803 }
1804 }
1805
1806 // Push fake entry to simplify cache bounds checks
1807 this.bMarks.push(s.length);
1808 this.eMarks.push(s.length);
1809 this.tShift.push(0);
1810
1811 this.lineMax = this.bMarks.length - 1; // don't count last fake line
1812}
1813
1814StateBlock.prototype.isEmpty = function isEmpty(line) {
1815 return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];
1816};
1817
1818StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) {
1819 for (var max = this.lineMax; from < max; from++) {
1820 if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {
1821 break;
1822 }
1823 }
1824 return from;
1825};
1826
1827// Skip spaces from given position.
1828StateBlock.prototype.skipSpaces = function skipSpaces(pos) {
1829 for (var max = this.src.length; pos < max; pos++) {
1830 if (this.src.charCodeAt(pos) !== 0x20/* space */) { break; }
1831 }
1832 return pos;
1833};
1834
1835// Skip char codes from given position
1836StateBlock.prototype.skipChars = function skipChars(pos, code) {
1837 for (var max = this.src.length; pos < max; pos++) {
1838 if (this.src.charCodeAt(pos) !== code) { break; }
1839 }
1840 return pos;
1841};
1842
1843// Skip char codes reverse from given position - 1
1844StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) {
1845 if (pos <= min) { return pos; }
1846
1847 while (pos > min) {
1848 if (code !== this.src.charCodeAt(--pos)) { return pos + 1; }
1849 }
1850 return pos;
1851};
1852
1853// cut lines range from source.
1854StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) {
1855 var i, first, last, queue, shift,
1856 line = begin;
1857
1858 if (begin >= end) {
1859 return '';
1860 }
1861
1862 // Opt: don't use push queue for single line;
1863 if (line + 1 === end) {
1864 first = this.bMarks[line] + Math.min(this.tShift[line], indent);
1865 last = keepLastLF ? this.eMarks[line] + 1 : this.eMarks[line];
1866 return this.src.slice(first, last);
1867 }
1868
1869 queue = new Array(end - begin);
1870
1871 for (i = 0; line < end; line++, i++) {
1872 shift = this.tShift[line];
1873 if (shift > indent) { shift = indent; }
1874 if (shift < 0) { shift = 0; }
1875
1876 first = this.bMarks[line] + shift;
1877
1878 if (line + 1 < end || keepLastLF) {
1879 // No need for bounds check because we have fake entry on tail.
1880 last = this.eMarks[line] + 1;
1881 } else {
1882 last = this.eMarks[line];
1883 }
1884
1885 queue[i] = this.src.slice(first, last);
1886 }
1887
1888 return queue.join('');
1889};
1890
1891// Code block (4 spaces padded)
1892
1893function code(state, startLine, endLine/*, silent*/) {
1894 var nextLine, last;
1895
1896 if (state.tShift[startLine] - state.blkIndent < 4) { return false; }
1897
1898 last = nextLine = startLine + 1;
1899
1900 while (nextLine < endLine) {
1901 if (state.isEmpty(nextLine)) {
1902 nextLine++;
1903 continue;
1904 }
1905 if (state.tShift[nextLine] - state.blkIndent >= 4) {
1906 nextLine++;
1907 last = nextLine;
1908 continue;
1909 }
1910 break;
1911 }
1912
1913 state.line = nextLine;
1914 state.tokens.push({
1915 type: 'code',
1916 content: state.getLines(startLine, last, 4 + state.blkIndent, true),
1917 block: true,
1918 lines: [ startLine, state.line ],
1919 level: state.level
1920 });
1921
1922 return true;
1923}
1924
1925// fences (``` lang, ~~~ lang)
1926
1927function fences(state, startLine, endLine, silent) {
1928 var marker, len, params, nextLine, mem,
1929 haveEndMarker = false,
1930 pos = state.bMarks[startLine] + state.tShift[startLine],
1931 max = state.eMarks[startLine];
1932
1933 if (pos + 3 > max) { return false; }
1934
1935 marker = state.src.charCodeAt(pos);
1936
1937 if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
1938 return false;
1939 }
1940
1941 // scan marker length
1942 mem = pos;
1943 pos = state.skipChars(pos, marker);
1944
1945 len = pos - mem;
1946
1947 if (len < 3) { return false; }
1948
1949 params = state.src.slice(pos, max).trim();
1950
1951 if (params.indexOf('`') >= 0) { return false; }
1952
1953 // Since start is found, we can report success here in validation mode
1954 if (silent) { return true; }
1955
1956 // search end of block
1957 nextLine = startLine;
1958
1959 for (;;) {
1960 nextLine++;
1961 if (nextLine >= endLine) {
1962 // unclosed block should be autoclosed by end of document.
1963 // also block seems to be autoclosed by end of parent
1964 break;
1965 }
1966
1967 pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
1968 max = state.eMarks[nextLine];
1969
1970 if (pos < max && state.tShift[nextLine] < state.blkIndent) {
1971 // non-empty line with negative indent should stop the list:
1972 // - ```
1973 // test
1974 break;
1975 }
1976
1977 if (state.src.charCodeAt(pos) !== marker) { continue; }
1978
1979 if (state.tShift[nextLine] - state.blkIndent >= 4) {
1980 // closing fence should be indented less than 4 spaces
1981 continue;
1982 }
1983
1984 pos = state.skipChars(pos, marker);
1985
1986 // closing code fence must be at least as long as the opening one
1987 if (pos - mem < len) { continue; }
1988
1989 // make sure tail has spaces only
1990 pos = state.skipSpaces(pos);
1991
1992 if (pos < max) { continue; }
1993
1994 haveEndMarker = true;
1995 // found!
1996 break;
1997 }
1998
1999 // If a fence has heading spaces, they should be removed from its inner block
2000 len = state.tShift[startLine];
2001
2002 state.line = nextLine + (haveEndMarker ? 1 : 0);
2003 state.tokens.push({
2004 type: 'fence',
2005 params: params,
2006 content: state.getLines(startLine + 1, nextLine, len, true),
2007 lines: [ startLine, state.line ],
2008 level: state.level
2009 });
2010
2011 return true;
2012}
2013
2014// Block quotes
2015
2016function blockquote(state, startLine, endLine, silent) {
2017 var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines,
2018 terminatorRules,
2019 i, l, terminate,
2020 pos = state.bMarks[startLine] + state.tShift[startLine],
2021 max = state.eMarks[startLine];
2022
2023 if (pos > max) { return false; }
2024
2025 // check the block quote marker
2026 if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; }
2027
2028 if (state.level >= state.options.maxNesting) { return false; }
2029
2030 // we know that it's going to be a valid blockquote,
2031 // so no point trying to find the end of it in silent mode
2032 if (silent) { return true; }
2033
2034 // skip one optional space after '>'
2035 if (state.src.charCodeAt(pos) === 0x20) { pos++; }
2036
2037 oldIndent = state.blkIndent;
2038 state.blkIndent = 0;
2039
2040 oldBMarks = [ state.bMarks[startLine] ];
2041 state.bMarks[startLine] = pos;
2042
2043 // check if we have an empty blockquote
2044 pos = pos < max ? state.skipSpaces(pos) : pos;
2045 lastLineEmpty = pos >= max;
2046
2047 oldTShift = [ state.tShift[startLine] ];
2048 state.tShift[startLine] = pos - state.bMarks[startLine];
2049
2050 terminatorRules = state.parser.ruler.getRules('blockquote');
2051
2052 // Search the end of the block
2053 //
2054 // Block ends with either:
2055 // 1. an empty line outside:
2056 // ```
2057 // > test
2058 //
2059 // ```
2060 // 2. an empty line inside:
2061 // ```
2062 // >
2063 // test
2064 // ```
2065 // 3. another tag
2066 // ```
2067 // > test
2068 // - - -
2069 // ```
2070 for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
2071 pos = state.bMarks[nextLine] + state.tShift[nextLine];
2072 max = state.eMarks[nextLine];
2073
2074 if (pos >= max) {
2075 // Case 1: line is not inside the blockquote, and this line is empty.
2076 break;
2077 }
2078
2079 if (state.src.charCodeAt(pos++) === 0x3E/* > */) {
2080 // This line is inside the blockquote.
2081
2082 // skip one optional space after '>'
2083 if (state.src.charCodeAt(pos) === 0x20) { pos++; }
2084
2085 oldBMarks.push(state.bMarks[nextLine]);
2086 state.bMarks[nextLine] = pos;
2087
2088 pos = pos < max ? state.skipSpaces(pos) : pos;
2089 lastLineEmpty = pos >= max;
2090
2091 oldTShift.push(state.tShift[nextLine]);
2092 state.tShift[nextLine] = pos - state.bMarks[nextLine];
2093 continue;
2094 }
2095
2096 // Case 2: line is not inside the blockquote, and the last line was empty.
2097 if (lastLineEmpty) { break; }
2098
2099 // Case 3: another tag found.
2100 terminate = false;
2101 for (i = 0, l = terminatorRules.length; i < l; i++) {
2102 if (terminatorRules[i](state, nextLine, endLine, true)) {
2103 terminate = true;
2104 break;
2105 }
2106 }
2107 if (terminate) { break; }
2108
2109 oldBMarks.push(state.bMarks[nextLine]);
2110 oldTShift.push(state.tShift[nextLine]);
2111
2112 // A negative number means that this is a paragraph continuation;
2113 //
2114 // Any negative number will do the job here, but it's better for it
2115 // to be large enough to make any bugs obvious.
2116 state.tShift[nextLine] = -1337;
2117 }
2118
2119 oldParentType = state.parentType;
2120 state.parentType = 'blockquote';
2121 state.tokens.push({
2122 type: 'blockquote_open',
2123 lines: lines = [ startLine, 0 ],
2124 level: state.level++
2125 });
2126 state.parser.tokenize(state, startLine, nextLine);
2127 state.tokens.push({
2128 type: 'blockquote_close',
2129 level: --state.level
2130 });
2131 state.parentType = oldParentType;
2132 lines[1] = state.line;
2133
2134 // Restore original tShift; this might not be necessary since the parser
2135 // has already been here, but just to make sure we can do that.
2136 for (i = 0; i < oldTShift.length; i++) {
2137 state.bMarks[i + startLine] = oldBMarks[i];
2138 state.tShift[i + startLine] = oldTShift[i];
2139 }
2140 state.blkIndent = oldIndent;
2141
2142 return true;
2143}
2144
2145// Horizontal rule
2146
2147function hr(state, startLine, endLine, silent) {
2148 var marker, cnt, ch,
2149 pos = state.bMarks[startLine],
2150 max = state.eMarks[startLine];
2151
2152 pos += state.tShift[startLine];
2153
2154 if (pos > max) { return false; }
2155
2156 marker = state.src.charCodeAt(pos++);
2157
2158 // Check hr marker
2159 if (marker !== 0x2A/* * */ &&
2160 marker !== 0x2D/* - */ &&
2161 marker !== 0x5F/* _ */) {
2162 return false;
2163 }
2164
2165 // markers can be mixed with spaces, but there should be at least 3 one
2166
2167 cnt = 1;
2168 while (pos < max) {
2169 ch = state.src.charCodeAt(pos++);
2170 if (ch !== marker && ch !== 0x20/* space */) { return false; }
2171 if (ch === marker) { cnt++; }
2172 }
2173
2174 if (cnt < 3) { return false; }
2175
2176 if (silent) { return true; }
2177
2178 state.line = startLine + 1;
2179 state.tokens.push({
2180 type: 'hr',
2181 lines: [ startLine, state.line ],
2182 level: state.level
2183 });
2184
2185 return true;
2186}
2187
2188// Lists
2189
2190// Search `[-+*][\n ]`, returns next pos arter marker on success
2191// or -1 on fail.
2192function skipBulletListMarker(state, startLine) {
2193 var marker, pos, max;
2194
2195 pos = state.bMarks[startLine] + state.tShift[startLine];
2196 max = state.eMarks[startLine];
2197
2198 if (pos >= max) { return -1; }
2199
2200 marker = state.src.charCodeAt(pos++);
2201 // Check bullet
2202 if (marker !== 0x2A/* * */ &&
2203 marker !== 0x2D/* - */ &&
2204 marker !== 0x2B/* + */) {
2205 return -1;
2206 }
2207
2208 if (pos < max && state.src.charCodeAt(pos) !== 0x20) {
2209 // " 1.test " - is not a list item
2210 return -1;
2211 }
2212
2213 return pos;
2214}
2215
2216// Search `\d+[.)][\n ]`, returns next pos arter marker on success
2217// or -1 on fail.
2218function skipOrderedListMarker(state, startLine) {
2219 var ch,
2220 pos = state.bMarks[startLine] + state.tShift[startLine],
2221 max = state.eMarks[startLine];
2222
2223 if (pos + 1 >= max) { return -1; }
2224
2225 ch = state.src.charCodeAt(pos++);
2226
2227 if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; }
2228
2229 for (;;) {
2230 // EOL -> fail
2231 if (pos >= max) { return -1; }
2232
2233 ch = state.src.charCodeAt(pos++);
2234
2235 if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) {
2236 continue;
2237 }
2238
2239 // found valid marker
2240 if (ch === 0x29/* ) */ || ch === 0x2e/* . */) {
2241 break;
2242 }
2243
2244 return -1;
2245 }
2246
2247
2248 if (pos < max && state.src.charCodeAt(pos) !== 0x20/* space */) {
2249 // " 1.test " - is not a list item
2250 return -1;
2251 }
2252 return pos;
2253}
2254
2255function markTightParagraphs(state, idx) {
2256 var i, l,
2257 level = state.level + 2;
2258
2259 for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
2260 if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
2261 state.tokens[i + 2].tight = true;
2262 state.tokens[i].tight = true;
2263 i += 2;
2264 }
2265 }
2266}
2267
2268
2269function list(state, startLine, endLine, silent) {
2270 var nextLine,
2271 indent,
2272 oldTShift,
2273 oldIndent,
2274 oldTight,
2275 oldParentType,
2276 start,
2277 posAfterMarker,
2278 max,
2279 indentAfterMarker,
2280 markerValue,
2281 markerCharCode,
2282 isOrdered,
2283 contentStart,
2284 listTokIdx,
2285 prevEmptyEnd,
2286 listLines,
2287 itemLines,
2288 tight = true,
2289 terminatorRules,
2290 i, l, terminate;
2291
2292 // Detect list type and position after marker
2293 if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) {
2294 isOrdered = true;
2295 } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) {
2296 isOrdered = false;
2297 } else {
2298 return false;
2299 }
2300
2301 if (state.level >= state.options.maxNesting) { return false; }
2302
2303 // We should terminate list on style change. Remember first one to compare.
2304 markerCharCode = state.src.charCodeAt(posAfterMarker - 1);
2305
2306 // For validation mode we can terminate immediately
2307 if (silent) { return true; }
2308
2309 // Start list
2310 listTokIdx = state.tokens.length;
2311
2312 if (isOrdered) {
2313 start = state.bMarks[startLine] + state.tShift[startLine];
2314 markerValue = Number(state.src.substr(start, posAfterMarker - start - 1));
2315
2316 state.tokens.push({
2317 type: 'ordered_list_open',
2318 order: markerValue,
2319 lines: listLines = [ startLine, 0 ],
2320 level: state.level++
2321 });
2322
2323 } else {
2324 state.tokens.push({
2325 type: 'bullet_list_open',
2326 lines: listLines = [ startLine, 0 ],
2327 level: state.level++
2328 });
2329 }
2330
2331 //
2332 // Iterate list items
2333 //
2334
2335 nextLine = startLine;
2336 prevEmptyEnd = false;
2337 terminatorRules = state.parser.ruler.getRules('list');
2338
2339 while (nextLine < endLine) {
2340 contentStart = state.skipSpaces(posAfterMarker);
2341 max = state.eMarks[nextLine];
2342
2343 if (contentStart >= max) {
2344 // trimming space in "- \n 3" case, indent is 1 here
2345 indentAfterMarker = 1;
2346 } else {
2347 indentAfterMarker = contentStart - posAfterMarker;
2348 }
2349
2350 // If we have more than 4 spaces, the indent is 1
2351 // (the rest is just indented code block)
2352 if (indentAfterMarker > 4) { indentAfterMarker = 1; }
2353
2354 // If indent is less than 1, assume that it's one, example:
2355 // "-\n test"
2356 if (indentAfterMarker < 1) { indentAfterMarker = 1; }
2357
2358 // " - test"
2359 // ^^^^^ - calculating total length of this thing
2360 indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker;
2361
2362 // Run subparser & write tokens
2363 state.tokens.push({
2364 type: 'list_item_open',
2365 lines: itemLines = [ startLine, 0 ],
2366 level: state.level++
2367 });
2368
2369 oldIndent = state.blkIndent;
2370 oldTight = state.tight;
2371 oldTShift = state.tShift[startLine];
2372 oldParentType = state.parentType;
2373 state.tShift[startLine] = contentStart - state.bMarks[startLine];
2374 state.blkIndent = indent;
2375 state.tight = true;
2376 state.parentType = 'list';
2377
2378 state.parser.tokenize(state, startLine, endLine, true);
2379
2380 // If any of list item is tight, mark list as tight
2381 if (!state.tight || prevEmptyEnd) {
2382 tight = false;
2383 }
2384 // Item become loose if finish with empty line,
2385 // but we should filter last element, because it means list finish
2386 prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1);
2387
2388 state.blkIndent = oldIndent;
2389 state.tShift[startLine] = oldTShift;
2390 state.tight = oldTight;
2391 state.parentType = oldParentType;
2392
2393 state.tokens.push({
2394 type: 'list_item_close',
2395 level: --state.level
2396 });
2397
2398 nextLine = startLine = state.line;
2399 itemLines[1] = nextLine;
2400 contentStart = state.bMarks[startLine];
2401
2402 if (nextLine >= endLine) { break; }
2403
2404 if (state.isEmpty(nextLine)) {
2405 break;
2406 }
2407
2408 //
2409 // Try to check if list is terminated or continued.
2410 //
2411 if (state.tShift[nextLine] < state.blkIndent) { break; }
2412
2413 // fail if terminating block found
2414 terminate = false;
2415 for (i = 0, l = terminatorRules.length; i < l; i++) {
2416 if (terminatorRules[i](state, nextLine, endLine, true)) {
2417 terminate = true;
2418 break;
2419 }
2420 }
2421 if (terminate) { break; }
2422
2423 // fail if list has another type
2424 if (isOrdered) {
2425 posAfterMarker = skipOrderedListMarker(state, nextLine);
2426 if (posAfterMarker < 0) { break; }
2427 } else {
2428 posAfterMarker = skipBulletListMarker(state, nextLine);
2429 if (posAfterMarker < 0) { break; }
2430 }
2431
2432 if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; }
2433 }
2434
2435 // Finilize list
2436 state.tokens.push({
2437 type: isOrdered ? 'ordered_list_close' : 'bullet_list_close',
2438 level: --state.level
2439 });
2440 listLines[1] = nextLine;
2441
2442 state.line = nextLine;
2443
2444 // mark paragraphs tight if needed
2445 if (tight) {
2446 markTightParagraphs(state, listTokIdx);
2447 }
2448
2449 return true;
2450}
2451
2452// Process footnote reference list
2453
2454function footnote(state, startLine, endLine, silent) {
2455 var oldBMark, oldTShift, oldParentType, pos, label,
2456 start = state.bMarks[startLine] + state.tShift[startLine],
2457 max = state.eMarks[startLine];
2458
2459 // line should be at least 5 chars - "[^x]:"
2460 if (start + 4 > max) { return false; }
2461
2462 if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; }
2463 if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; }
2464 if (state.level >= state.options.maxNesting) { return false; }
2465
2466 for (pos = start + 2; pos < max; pos++) {
2467 if (state.src.charCodeAt(pos) === 0x20) { return false; }
2468 if (state.src.charCodeAt(pos) === 0x5D /* ] */) {
2469 break;
2470 }
2471 }
2472
2473 if (pos === start + 2) { return false; } // no empty footnote labels
2474 if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) { return false; }
2475 if (silent) { return true; }
2476 pos++;
2477
2478 if (!state.env.footnotes) { state.env.footnotes = {}; }
2479 if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; }
2480 label = state.src.slice(start + 2, pos - 2);
2481 state.env.footnotes.refs[':' + label] = -1;
2482
2483 state.tokens.push({
2484 type: 'footnote_reference_open',
2485 label: label,
2486 level: state.level++
2487 });
2488
2489 oldBMark = state.bMarks[startLine];
2490 oldTShift = state.tShift[startLine];
2491 oldParentType = state.parentType;
2492 state.tShift[startLine] = state.skipSpaces(pos) - pos;
2493 state.bMarks[startLine] = pos;
2494 state.blkIndent += 4;
2495 state.parentType = 'footnote';
2496
2497 if (state.tShift[startLine] < state.blkIndent) {
2498 state.tShift[startLine] += state.blkIndent;
2499 state.bMarks[startLine] -= state.blkIndent;
2500 }
2501
2502 state.parser.tokenize(state, startLine, endLine, true);
2503
2504 state.parentType = oldParentType;
2505 state.blkIndent -= 4;
2506 state.tShift[startLine] = oldTShift;
2507 state.bMarks[startLine] = oldBMark;
2508
2509 state.tokens.push({
2510 type: 'footnote_reference_close',
2511 level: --state.level
2512 });
2513
2514 return true;
2515}
2516
2517// heading (#, ##, ...)
2518
2519function heading(state, startLine, endLine, silent) {
2520 var ch, level, tmp,
2521 pos = state.bMarks[startLine] + state.tShift[startLine],
2522 max = state.eMarks[startLine];
2523
2524 if (pos >= max) { return false; }
2525
2526 ch = state.src.charCodeAt(pos);
2527
2528 if (ch !== 0x23/* # */ || pos >= max) { return false; }
2529
2530 // count heading level
2531 level = 1;
2532 ch = state.src.charCodeAt(++pos);
2533 while (ch === 0x23/* # */ && pos < max && level <= 6) {
2534 level++;
2535 ch = state.src.charCodeAt(++pos);
2536 }
2537
2538 if (level > 6 || (pos < max && ch !== 0x20/* space */)) { return false; }
2539
2540 if (silent) { return true; }
2541
2542 // Let's cut tails like ' ### ' from the end of string
2543
2544 max = state.skipCharsBack(max, 0x20, pos); // space
2545 tmp = state.skipCharsBack(max, 0x23, pos); // #
2546 if (tmp > pos && state.src.charCodeAt(tmp - 1) === 0x20/* space */) {
2547 max = tmp;
2548 }
2549
2550 state.line = startLine + 1;
2551
2552 state.tokens.push({ type: 'heading_open',
2553 hLevel: level,
2554 lines: [ startLine, state.line ],
2555 level: state.level
2556 });
2557
2558 // only if header is not empty
2559 if (pos < max) {
2560 state.tokens.push({
2561 type: 'inline',
2562 content: state.src.slice(pos, max).trim(),
2563 level: state.level + 1,
2564 lines: [ startLine, state.line ],
2565 children: []
2566 });
2567 }
2568 state.tokens.push({ type: 'heading_close', hLevel: level, level: state.level });
2569
2570 return true;
2571}
2572
2573// lheading (---, ===)
2574
2575function lheading(state, startLine, endLine/*, silent*/) {
2576 var marker, pos, max,
2577 next = startLine + 1;
2578
2579 if (next >= endLine) { return false; }
2580 if (state.tShift[next] < state.blkIndent) { return false; }
2581
2582 // Scan next line
2583
2584 if (state.tShift[next] - state.blkIndent > 3) { return false; }
2585
2586 pos = state.bMarks[next] + state.tShift[next];
2587 max = state.eMarks[next];
2588
2589 if (pos >= max) { return false; }
2590
2591 marker = state.src.charCodeAt(pos);
2592
2593 if (marker !== 0x2D/* - */ && marker !== 0x3D/* = */) { return false; }
2594
2595 pos = state.skipChars(pos, marker);
2596
2597 pos = state.skipSpaces(pos);
2598
2599 if (pos < max) { return false; }
2600
2601 pos = state.bMarks[startLine] + state.tShift[startLine];
2602
2603 state.line = next + 1;
2604 state.tokens.push({
2605 type: 'heading_open',
2606 hLevel: marker === 0x3D/* = */ ? 1 : 2,
2607 lines: [ startLine, state.line ],
2608 level: state.level
2609 });
2610 state.tokens.push({
2611 type: 'inline',
2612 content: state.src.slice(pos, state.eMarks[startLine]).trim(),
2613 level: state.level + 1,
2614 lines: [ startLine, state.line - 1 ],
2615 children: []
2616 });
2617 state.tokens.push({
2618 type: 'heading_close',
2619 hLevel: marker === 0x3D/* = */ ? 1 : 2,
2620 level: state.level
2621 });
2622
2623 return true;
2624}
2625
2626// List of valid html blocks names, accorting to commonmark spec
2627// http://jgm.github.io/CommonMark/spec.html#html-blocks
2628
2629var html_blocks = {};
2630
2631[
2632 'article',
2633 'aside',
2634 'button',
2635 'blockquote',
2636 'body',
2637 'canvas',
2638 'caption',
2639 'col',
2640 'colgroup',
2641 'dd',
2642 'div',
2643 'dl',
2644 'dt',
2645 'embed',
2646 'fieldset',
2647 'figcaption',
2648 'figure',
2649 'footer',
2650 'form',
2651 'h1',
2652 'h2',
2653 'h3',
2654 'h4',
2655 'h5',
2656 'h6',
2657 'header',
2658 'hgroup',
2659 'hr',
2660 'iframe',
2661 'li',
2662 'map',
2663 'object',
2664 'ol',
2665 'output',
2666 'p',
2667 'pre',
2668 'progress',
2669 'script',
2670 'section',
2671 'style',
2672 'table',
2673 'tbody',
2674 'td',
2675 'textarea',
2676 'tfoot',
2677 'th',
2678 'tr',
2679 'thead',
2680 'ul',
2681 'video'
2682].forEach(function (name) { html_blocks[name] = true; });
2683
2684// HTML block
2685
2686
2687var HTML_TAG_OPEN_RE = /^<([a-zA-Z]{1,15})[\s\/>]/;
2688var HTML_TAG_CLOSE_RE = /^<\/([a-zA-Z]{1,15})[\s>]/;
2689
2690function isLetter$1(ch) {
2691 /*eslint no-bitwise:0*/
2692 var lc = ch | 0x20; // to lower case
2693 return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */);
2694}
2695
2696function htmlblock(state, startLine, endLine, silent) {
2697 var ch, match, nextLine,
2698 pos = state.bMarks[startLine],
2699 max = state.eMarks[startLine],
2700 shift = state.tShift[startLine];
2701
2702 pos += shift;
2703
2704 if (!state.options.html) { return false; }
2705
2706 if (shift > 3 || pos + 2 >= max) { return false; }
2707
2708 if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }
2709
2710 ch = state.src.charCodeAt(pos + 1);
2711
2712 if (ch === 0x21/* ! */ || ch === 0x3F/* ? */) {
2713 // Directive start / comment start / processing instruction start
2714 if (silent) { return true; }
2715
2716 } else if (ch === 0x2F/* / */ || isLetter$1(ch)) {
2717
2718 // Probably start or end of tag
2719 if (ch === 0x2F/* \ */) {
2720 // closing tag
2721 match = state.src.slice(pos, max).match(HTML_TAG_CLOSE_RE);
2722 if (!match) { return false; }
2723 } else {
2724 // opening tag
2725 match = state.src.slice(pos, max).match(HTML_TAG_OPEN_RE);
2726 if (!match) { return false; }
2727 }
2728 // Make sure tag name is valid
2729 if (html_blocks[match[1].toLowerCase()] !== true) { return false; }
2730 if (silent) { return true; }
2731
2732 } else {
2733 return false;
2734 }
2735
2736 // If we are here - we detected HTML block.
2737 // Let's roll down till empty line (block end).
2738 nextLine = startLine + 1;
2739 while (nextLine < state.lineMax && !state.isEmpty(nextLine)) {
2740 nextLine++;
2741 }
2742
2743 state.line = nextLine;
2744 state.tokens.push({
2745 type: 'htmlblock',
2746 level: state.level,
2747 lines: [ startLine, state.line ],
2748 content: state.getLines(startLine, nextLine, 0, true)
2749 });
2750
2751 return true;
2752}
2753
2754// GFM table, non-standard
2755
2756function getLine(state, line) {
2757 var pos = state.bMarks[line] + state.blkIndent,
2758 max = state.eMarks[line];
2759
2760 return state.src.substr(pos, max - pos);
2761}
2762
2763function table(state, startLine, endLine, silent) {
2764 var ch, lineText, pos, i, nextLine, rows, cell,
2765 aligns, t, tableLines, tbodyLines;
2766
2767 // should have at least three lines
2768 if (startLine + 2 > endLine) { return false; }
2769
2770 nextLine = startLine + 1;
2771
2772 if (state.tShift[nextLine] < state.blkIndent) { return false; }
2773
2774 // first character of the second line should be '|' or '-'
2775
2776 pos = state.bMarks[nextLine] + state.tShift[nextLine];
2777 if (pos >= state.eMarks[nextLine]) { return false; }
2778
2779 ch = state.src.charCodeAt(pos);
2780 if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; }
2781
2782 lineText = getLine(state, startLine + 1);
2783 if (!/^[-:| ]+$/.test(lineText)) { return false; }
2784
2785 rows = lineText.split('|');
2786 if (rows <= 2) { return false; }
2787 aligns = [];
2788 for (i = 0; i < rows.length; i++) {
2789 t = rows[i].trim();
2790 if (!t) {
2791 // allow empty columns before and after table, but not in between columns;
2792 // e.g. allow ` |---| `, disallow ` ---||--- `
2793 if (i === 0 || i === rows.length - 1) {
2794 continue;
2795 } else {
2796 return false;
2797 }
2798 }
2799
2800 if (!/^:?-+:?$/.test(t)) { return false; }
2801 if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
2802 aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');
2803 } else if (t.charCodeAt(0) === 0x3A/* : */) {
2804 aligns.push('left');
2805 } else {
2806 aligns.push('');
2807 }
2808 }
2809
2810 lineText = getLine(state, startLine).trim();
2811 if (lineText.indexOf('|') === -1) { return false; }
2812 rows = lineText.replace(/^\||\|$/g, '').split('|');
2813 if (aligns.length !== rows.length) { return false; }
2814 if (silent) { return true; }
2815
2816 state.tokens.push({
2817 type: 'table_open',
2818 lines: tableLines = [ startLine, 0 ],
2819 level: state.level++
2820 });
2821 state.tokens.push({
2822 type: 'thead_open',
2823 lines: [ startLine, startLine + 1 ],
2824 level: state.level++
2825 });
2826
2827 state.tokens.push({
2828 type: 'tr_open',
2829 lines: [ startLine, startLine + 1 ],
2830 level: state.level++
2831 });
2832 for (i = 0; i < rows.length; i++) {
2833 state.tokens.push({
2834 type: 'th_open',
2835 align: aligns[i],
2836 lines: [ startLine, startLine + 1 ],
2837 level: state.level++
2838 });
2839 state.tokens.push({
2840 type: 'inline',
2841 content: rows[i].trim(),
2842 lines: [ startLine, startLine + 1 ],
2843 level: state.level,
2844 children: []
2845 });
2846 state.tokens.push({ type: 'th_close', level: --state.level });
2847 }
2848 state.tokens.push({ type: 'tr_close', level: --state.level });
2849 state.tokens.push({ type: 'thead_close', level: --state.level });
2850
2851 state.tokens.push({
2852 type: 'tbody_open',
2853 lines: tbodyLines = [ startLine + 2, 0 ],
2854 level: state.level++
2855 });
2856
2857 for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
2858 if (state.tShift[nextLine] < state.blkIndent) { break; }
2859
2860 lineText = getLine(state, nextLine).trim();
2861 if (lineText.indexOf('|') === -1) { break; }
2862 rows = lineText.replace(/^\||\|$/g, '').split('|');
2863
2864 state.tokens.push({ type: 'tr_open', level: state.level++ });
2865 for (i = 0; i < rows.length; i++) {
2866 state.tokens.push({ type: 'td_open', align: aligns[i], level: state.level++ });
2867 // 0x7c === '|'
2868 cell = rows[i].substring(
2869 rows[i].charCodeAt(0) === 0x7c ? 1 : 0,
2870 rows[i].charCodeAt(rows[i].length - 1) === 0x7c ? rows[i].length - 1 : rows[i].length
2871 ).trim();
2872 state.tokens.push({
2873 type: 'inline',
2874 content: cell,
2875 level: state.level,
2876 children: []
2877 });
2878 state.tokens.push({ type: 'td_close', level: --state.level });
2879 }
2880 state.tokens.push({ type: 'tr_close', level: --state.level });
2881 }
2882 state.tokens.push({ type: 'tbody_close', level: --state.level });
2883 state.tokens.push({ type: 'table_close', level: --state.level });
2884
2885 tableLines[1] = tbodyLines[1] = nextLine;
2886 state.line = nextLine;
2887 return true;
2888}
2889
2890// Definition lists
2891
2892// Search `[:~][\n ]`, returns next pos after marker on success
2893// or -1 on fail.
2894function skipMarker(state, line) {
2895 var pos, marker,
2896 start = state.bMarks[line] + state.tShift[line],
2897 max = state.eMarks[line];
2898
2899 if (start >= max) { return -1; }
2900
2901 // Check bullet
2902 marker = state.src.charCodeAt(start++);
2903 if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }
2904
2905 pos = state.skipSpaces(start);
2906
2907 // require space after ":"
2908 if (start === pos) { return -1; }
2909
2910 // no empty definitions, e.g. " : "
2911 if (pos >= max) { return -1; }
2912
2913 return pos;
2914}
2915
2916function markTightParagraphs$1(state, idx) {
2917 var i, l,
2918 level = state.level + 2;
2919
2920 for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
2921 if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
2922 state.tokens[i + 2].tight = true;
2923 state.tokens[i].tight = true;
2924 i += 2;
2925 }
2926 }
2927}
2928
2929function deflist(state, startLine, endLine, silent) {
2930 var contentStart,
2931 ddLine,
2932 dtLine,
2933 itemLines,
2934 listLines,
2935 listTokIdx,
2936 nextLine,
2937 oldIndent,
2938 oldDDIndent,
2939 oldParentType,
2940 oldTShift,
2941 oldTight,
2942 prevEmptyEnd,
2943 tight;
2944
2945 if (silent) {
2946 // quirk: validation mode validates a dd block only, not a whole deflist
2947 if (state.ddIndent < 0) { return false; }
2948 return skipMarker(state, startLine) >= 0;
2949 }
2950
2951 nextLine = startLine + 1;
2952 if (state.isEmpty(nextLine)) {
2953 if (++nextLine > endLine) { return false; }
2954 }
2955
2956 if (state.tShift[nextLine] < state.blkIndent) { return false; }
2957 contentStart = skipMarker(state, nextLine);
2958 if (contentStart < 0) { return false; }
2959
2960 if (state.level >= state.options.maxNesting) { return false; }
2961
2962 // Start list
2963 listTokIdx = state.tokens.length;
2964
2965 state.tokens.push({
2966 type: 'dl_open',
2967 lines: listLines = [ startLine, 0 ],
2968 level: state.level++
2969 });
2970
2971 //
2972 // Iterate list items
2973 //
2974
2975 dtLine = startLine;
2976 ddLine = nextLine;
2977
2978 // One definition list can contain multiple DTs,
2979 // and one DT can be followed by multiple DDs.
2980 //
2981 // Thus, there is two loops here, and label is
2982 // needed to break out of the second one
2983 //
2984 /*eslint no-labels:0,block-scoped-var:0*/
2985 OUTER:
2986 for (;;) {
2987 tight = true;
2988 prevEmptyEnd = false;
2989
2990 state.tokens.push({
2991 type: 'dt_open',
2992 lines: [ dtLine, dtLine ],
2993 level: state.level++
2994 });
2995 state.tokens.push({
2996 type: 'inline',
2997 content: state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim(),
2998 level: state.level + 1,
2999 lines: [ dtLine, dtLine ],
3000 children: []
3001 });
3002 state.tokens.push({
3003 type: 'dt_close',
3004 level: --state.level
3005 });
3006
3007 for (;;) {
3008 state.tokens.push({
3009 type: 'dd_open',
3010 lines: itemLines = [ nextLine, 0 ],
3011 level: state.level++
3012 });
3013
3014 oldTight = state.tight;
3015 oldDDIndent = state.ddIndent;
3016 oldIndent = state.blkIndent;
3017 oldTShift = state.tShift[ddLine];
3018 oldParentType = state.parentType;
3019 state.blkIndent = state.ddIndent = state.tShift[ddLine] + 2;
3020 state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
3021 state.tight = true;
3022 state.parentType = 'deflist';
3023
3024 state.parser.tokenize(state, ddLine, endLine, true);
3025
3026 // If any of list item is tight, mark list as tight
3027 if (!state.tight || prevEmptyEnd) {
3028 tight = false;
3029 }
3030 // Item become loose if finish with empty line,
3031 // but we should filter last element, because it means list finish
3032 prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);
3033
3034 state.tShift[ddLine] = oldTShift;
3035 state.tight = oldTight;
3036 state.parentType = oldParentType;
3037 state.blkIndent = oldIndent;
3038 state.ddIndent = oldDDIndent;
3039
3040 state.tokens.push({
3041 type: 'dd_close',
3042 level: --state.level
3043 });
3044
3045 itemLines[1] = nextLine = state.line;
3046
3047 if (nextLine >= endLine) { break OUTER; }
3048
3049 if (state.tShift[nextLine] < state.blkIndent) { break OUTER; }
3050 contentStart = skipMarker(state, nextLine);
3051 if (contentStart < 0) { break; }
3052
3053 ddLine = nextLine;
3054
3055 // go to the next loop iteration:
3056 // insert DD tag and repeat checking
3057 }
3058
3059 if (nextLine >= endLine) { break; }
3060 dtLine = nextLine;
3061
3062 if (state.isEmpty(dtLine)) { break; }
3063 if (state.tShift[dtLine] < state.blkIndent) { break; }
3064
3065 ddLine = dtLine + 1;
3066 if (ddLine >= endLine) { break; }
3067 if (state.isEmpty(ddLine)) { ddLine++; }
3068 if (ddLine >= endLine) { break; }
3069
3070 if (state.tShift[ddLine] < state.blkIndent) { break; }
3071 contentStart = skipMarker(state, ddLine);
3072 if (contentStart < 0) { break; }
3073
3074 // go to the next loop iteration:
3075 // insert DT and DD tags and repeat checking
3076 }
3077
3078 // Finilize list
3079 state.tokens.push({
3080 type: 'dl_close',
3081 level: --state.level
3082 });
3083 listLines[1] = nextLine;
3084
3085 state.line = nextLine;
3086
3087 // mark paragraphs tight if needed
3088 if (tight) {
3089 markTightParagraphs$1(state, listTokIdx);
3090 }
3091
3092 return true;
3093}
3094
3095// Paragraph
3096
3097function paragraph(state, startLine/*, endLine*/) {
3098 var endLine, content, terminate, i, l,
3099 nextLine = startLine + 1,
3100 terminatorRules;
3101
3102 endLine = state.lineMax;
3103
3104 // jump line-by-line until empty one or EOF
3105 if (nextLine < endLine && !state.isEmpty(nextLine)) {
3106 terminatorRules = state.parser.ruler.getRules('paragraph');
3107
3108 for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
3109 // this would be a code block normally, but after paragraph
3110 // it's considered a lazy continuation regardless of what's there
3111 if (state.tShift[nextLine] - state.blkIndent > 3) { continue; }
3112
3113 // Some tags can terminate paragraph without empty line.
3114 terminate = false;
3115 for (i = 0, l = terminatorRules.length; i < l; i++) {
3116 if (terminatorRules[i](state, nextLine, endLine, true)) {
3117 terminate = true;
3118 break;
3119 }
3120 }
3121 if (terminate) { break; }
3122 }
3123 }
3124
3125 content = state.getLines(startLine, nextLine, state.blkIndent, false).trim();
3126
3127 state.line = nextLine;
3128 if (content.length) {
3129 state.tokens.push({
3130 type: 'paragraph_open',
3131 tight: false,
3132 lines: [ startLine, state.line ],
3133 level: state.level
3134 });
3135 state.tokens.push({
3136 type: 'inline',
3137 content: content,
3138 level: state.level + 1,
3139 lines: [ startLine, state.line ],
3140 children: []
3141 });
3142 state.tokens.push({
3143 type: 'paragraph_close',
3144 tight: false,
3145 level: state.level
3146 });
3147 }
3148
3149 return true;
3150}
3151
3152/**
3153 * Parser rules
3154 */
3155
3156var _rules$1 = [
3157 [ 'code', code ],
3158 [ 'fences', fences, [ 'paragraph', 'blockquote', 'list' ] ],
3159 [ 'blockquote', blockquote, [ 'paragraph', 'blockquote', 'list' ] ],
3160 [ 'hr', hr, [ 'paragraph', 'blockquote', 'list' ] ],
3161 [ 'list', list, [ 'paragraph', 'blockquote' ] ],
3162 [ 'footnote', footnote, [ 'paragraph' ] ],
3163 [ 'heading', heading, [ 'paragraph', 'blockquote' ] ],
3164 [ 'lheading', lheading ],
3165 [ 'htmlblock', htmlblock, [ 'paragraph', 'blockquote' ] ],
3166 [ 'table', table, [ 'paragraph' ] ],
3167 [ 'deflist', deflist, [ 'paragraph' ] ],
3168 [ 'paragraph', paragraph ]
3169];
3170
3171/**
3172 * Block Parser class
3173 *
3174 * @api private
3175 */
3176
3177function ParserBlock() {
3178 this.ruler = new Ruler();
3179 for (var i = 0; i < _rules$1.length; i++) {
3180 this.ruler.push(_rules$1[i][0], _rules$1[i][1], {
3181 alt: (_rules$1[i][2] || []).slice()
3182 });
3183 }
3184}
3185
3186/**
3187 * Generate tokens for the given input range.
3188 *
3189 * @param {Object} `state` Has properties like `src`, `parser`, `options` etc
3190 * @param {Number} `startLine`
3191 * @param {Number} `endLine`
3192 * @api private
3193 */
3194
3195ParserBlock.prototype.tokenize = function (state, startLine, endLine) {
3196 var rules = this.ruler.getRules('');
3197 var len = rules.length;
3198 var line = startLine;
3199 var hasEmptyLines = false;
3200 var ok, i;
3201
3202 while (line < endLine) {
3203 state.line = line = state.skipEmptyLines(line);
3204 if (line >= endLine) {
3205 break;
3206 }
3207
3208 // Termination condition for nested calls.
3209 // Nested calls currently used for blockquotes & lists
3210 if (state.tShift[line] < state.blkIndent) {
3211 break;
3212 }
3213
3214 // Try all possible rules.
3215 // On success, rule should:
3216 //
3217 // - update `state.line`
3218 // - update `state.tokens`
3219 // - return true
3220
3221 for (i = 0; i < len; i++) {
3222 ok = rules[i](state, line, endLine, false);
3223 if (ok) {
3224 break;
3225 }
3226 }
3227
3228 // set state.tight iff we had an empty line before current tag
3229 // i.e. latest empty line should not count
3230 state.tight = !hasEmptyLines;
3231
3232 // paragraph might "eat" one newline after it in nested lists
3233 if (state.isEmpty(state.line - 1)) {
3234 hasEmptyLines = true;
3235 }
3236
3237 line = state.line;
3238
3239 if (line < endLine && state.isEmpty(line)) {
3240 hasEmptyLines = true;
3241 line++;
3242
3243 // two empty lines should stop the parser in list mode
3244 if (line < endLine && state.parentType === 'list' && state.isEmpty(line)) { break; }
3245 state.line = line;
3246 }
3247 }
3248};
3249
3250var TABS_SCAN_RE = /[\n\t]/g;
3251var NEWLINES_RE = /\r[\n\u0085]|[\u2424\u2028\u0085]/g;
3252var SPACES_RE = /\u00a0/g;
3253
3254/**
3255 * Tokenize the given `str`.
3256 *
3257 * @param {String} `str` Source string
3258 * @param {Object} `options`
3259 * @param {Object} `env`
3260 * @param {Array} `outTokens`
3261 * @api private
3262 */
3263
3264ParserBlock.prototype.parse = function (str, options, env, outTokens) {
3265 var state, lineStart = 0, lastTabPos = 0;
3266 if (!str) { return []; }
3267
3268 // Normalize spaces
3269 str = str.replace(SPACES_RE, ' ');
3270
3271 // Normalize newlines
3272 str = str.replace(NEWLINES_RE, '\n');
3273
3274 // Replace tabs with proper number of spaces (1..4)
3275 if (str.indexOf('\t') >= 0) {
3276 str = str.replace(TABS_SCAN_RE, function (match, offset) {
3277 var result;
3278 if (str.charCodeAt(offset) === 0x0A) {
3279 lineStart = offset + 1;
3280 lastTabPos = 0;
3281 return match;
3282 }
3283 result = ' '.slice((offset - lineStart - lastTabPos) % 4);
3284 lastTabPos = offset - lineStart + 1;
3285 return result;
3286 });
3287 }
3288
3289 state = new StateBlock(str, this, options, env, outTokens);
3290 this.tokenize(state, state.line, state.lineMax);
3291};
3292
3293// Skip text characters for text token, place those to pending buffer
3294// and increment current pos
3295
3296// Rule to skip pure text
3297// '{}$%@~+=:' reserved for extentions
3298
3299function isTerminatorChar(ch) {
3300 switch (ch) {
3301 case 0x0A/* \n */:
3302 case 0x5C/* \ */:
3303 case 0x60/* ` */:
3304 case 0x2A/* * */:
3305 case 0x5F/* _ */:
3306 case 0x5E/* ^ */:
3307 case 0x5B/* [ */:
3308 case 0x5D/* ] */:
3309 case 0x21/* ! */:
3310 case 0x26/* & */:
3311 case 0x3C/* < */:
3312 case 0x3E/* > */:
3313 case 0x7B/* { */:
3314 case 0x7D/* } */:
3315 case 0x24/* $ */:
3316 case 0x25/* % */:
3317 case 0x40/* @ */:
3318 case 0x7E/* ~ */:
3319 case 0x2B/* + */:
3320 case 0x3D/* = */:
3321 case 0x3A/* : */:
3322 return true;
3323 default:
3324 return false;
3325 }
3326}
3327
3328function text(state, silent) {
3329 var pos = state.pos;
3330
3331 while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) {
3332 pos++;
3333 }
3334
3335 if (pos === state.pos) { return false; }
3336
3337 if (!silent) { state.pending += state.src.slice(state.pos, pos); }
3338
3339 state.pos = pos;
3340
3341 return true;
3342}
3343
3344// Proceess '\n'
3345
3346function newline(state, silent) {
3347 var pmax, max, pos = state.pos;
3348
3349 if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; }
3350
3351 pmax = state.pending.length - 1;
3352 max = state.posMax;
3353
3354 // ' \n' -> hardbreak
3355 // Lookup in pending chars is bad practice! Don't copy to other rules!
3356 // Pending string is stored in concat mode, indexed lookups will cause
3357 // convertion to flat mode.
3358 if (!silent) {
3359 if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) {
3360 if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) {
3361 // Strip out all trailing spaces on this line.
3362 for (var i = pmax - 2; i >= 0; i--) {
3363 if (state.pending.charCodeAt(i) !== 0x20) {
3364 state.pending = state.pending.substring(0, i + 1);
3365 break;
3366 }
3367 }
3368 state.push({
3369 type: 'hardbreak',
3370 level: state.level
3371 });
3372 } else {
3373 state.pending = state.pending.slice(0, -1);
3374 state.push({
3375 type: 'softbreak',
3376 level: state.level
3377 });
3378 }
3379
3380 } else {
3381 state.push({
3382 type: 'softbreak',
3383 level: state.level
3384 });
3385 }
3386 }
3387
3388 pos++;
3389
3390 // skip heading spaces for next line
3391 while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; }
3392
3393 state.pos = pos;
3394 return true;
3395}
3396
3397// Proceess escaped chars and hardbreaks
3398
3399var ESCAPED = [];
3400
3401for (var i = 0; i < 256; i++) { ESCAPED.push(0); }
3402
3403'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'
3404 .split('').forEach(function(ch) { ESCAPED[ch.charCodeAt(0)] = 1; });
3405
3406
3407function escape(state, silent) {
3408 var ch, pos = state.pos, max = state.posMax;
3409
3410 if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; }
3411
3412 pos++;
3413
3414 if (pos < max) {
3415 ch = state.src.charCodeAt(pos);
3416
3417 if (ch < 256 && ESCAPED[ch] !== 0) {
3418 if (!silent) { state.pending += state.src[pos]; }
3419 state.pos += 2;
3420 return true;
3421 }
3422
3423 if (ch === 0x0A) {
3424 if (!silent) {
3425 state.push({
3426 type: 'hardbreak',
3427 level: state.level
3428 });
3429 }
3430
3431 pos++;
3432 // skip leading whitespaces from next line
3433 while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; }
3434
3435 state.pos = pos;
3436 return true;
3437 }
3438 }
3439
3440 if (!silent) { state.pending += '\\'; }
3441 state.pos++;
3442 return true;
3443}
3444
3445// Parse backticks
3446
3447function backticks(state, silent) {
3448 var start, max, marker, matchStart, matchEnd,
3449 pos = state.pos,
3450 ch = state.src.charCodeAt(pos);
3451
3452 if (ch !== 0x60/* ` */) { return false; }
3453
3454 start = pos;
3455 pos++;
3456 max = state.posMax;
3457
3458 while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; }
3459
3460 marker = state.src.slice(start, pos);
3461
3462 matchStart = matchEnd = pos;
3463
3464 while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) {
3465 matchEnd = matchStart + 1;
3466
3467 while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; }
3468
3469 if (matchEnd - matchStart === marker.length) {
3470 if (!silent) {
3471 state.push({
3472 type: 'code',
3473 content: state.src.slice(pos, matchStart)
3474 .replace(/[ \n]+/g, ' ')
3475 .trim(),
3476 block: false,
3477 level: state.level
3478 });
3479 }
3480 state.pos = matchEnd;
3481 return true;
3482 }
3483 }
3484
3485 if (!silent) { state.pending += marker; }
3486 state.pos += marker.length;
3487 return true;
3488}
3489
3490// Process ~~deleted text~~
3491
3492function del(state, silent) {
3493 var found,
3494 pos,
3495 stack,
3496 max = state.posMax,
3497 start = state.pos,
3498 lastChar,
3499 nextChar;
3500
3501 if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; }
3502 if (silent) { return false; } // don't run any pairs in validation mode
3503 if (start + 4 >= max) { return false; }
3504 if (state.src.charCodeAt(start + 1) !== 0x7E/* ~ */) { return false; }
3505 if (state.level >= state.options.maxNesting) { return false; }
3506
3507 lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;
3508 nextChar = state.src.charCodeAt(start + 2);
3509
3510 if (lastChar === 0x7E/* ~ */) { return false; }
3511 if (nextChar === 0x7E/* ~ */) { return false; }
3512 if (nextChar === 0x20 || nextChar === 0x0A) { return false; }
3513
3514 pos = start + 2;
3515 while (pos < max && state.src.charCodeAt(pos) === 0x7E/* ~ */) { pos++; }
3516 if (pos > start + 3) {
3517 // sequence of 4+ markers taking as literal, same as in a emphasis
3518 state.pos += pos - start;
3519 if (!silent) { state.pending += state.src.slice(start, pos); }
3520 return true;
3521 }
3522
3523 state.pos = start + 2;
3524 stack = 1;
3525
3526 while (state.pos + 1 < max) {
3527 if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) {
3528 if (state.src.charCodeAt(state.pos + 1) === 0x7E/* ~ */) {
3529 lastChar = state.src.charCodeAt(state.pos - 1);
3530 nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1;
3531 if (nextChar !== 0x7E/* ~ */ && lastChar !== 0x7E/* ~ */) {
3532 if (lastChar !== 0x20 && lastChar !== 0x0A) {
3533 // closing '~~'
3534 stack--;
3535 } else if (nextChar !== 0x20 && nextChar !== 0x0A) {
3536 // opening '~~'
3537 stack++;
3538 } // else {
3539 // // standalone ' ~~ ' indented with spaces
3540 // }
3541 if (stack <= 0) {
3542 found = true;
3543 break;
3544 }
3545 }
3546 }
3547 }
3548
3549 state.parser.skipToken(state);
3550 }
3551
3552 if (!found) {
3553 // parser failed to find ending tag, so it's not valid emphasis
3554 state.pos = start;
3555 return false;
3556 }
3557
3558 // found!
3559 state.posMax = state.pos;
3560 state.pos = start + 2;
3561
3562 if (!silent) {
3563 state.push({ type: 'del_open', level: state.level++ });
3564 state.parser.tokenize(state);
3565 state.push({ type: 'del_close', level: --state.level });
3566 }
3567
3568 state.pos = state.posMax + 2;
3569 state.posMax = max;
3570 return true;
3571}
3572
3573// Process ++inserted text++
3574
3575function ins(state, silent) {
3576 var found,
3577 pos,
3578 stack,
3579 max = state.posMax,
3580 start = state.pos,
3581 lastChar,
3582 nextChar;
3583
3584 if (state.src.charCodeAt(start) !== 0x2B/* + */) { return false; }
3585 if (silent) { return false; } // don't run any pairs in validation mode
3586 if (start + 4 >= max) { return false; }
3587 if (state.src.charCodeAt(start + 1) !== 0x2B/* + */) { return false; }
3588 if (state.level >= state.options.maxNesting) { return false; }
3589
3590 lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;
3591 nextChar = state.src.charCodeAt(start + 2);
3592
3593 if (lastChar === 0x2B/* + */) { return false; }
3594 if (nextChar === 0x2B/* + */) { return false; }
3595 if (nextChar === 0x20 || nextChar === 0x0A) { return false; }
3596
3597 pos = start + 2;
3598 while (pos < max && state.src.charCodeAt(pos) === 0x2B/* + */) { pos++; }
3599 if (pos !== start + 2) {
3600 // sequence of 3+ markers taking as literal, same as in a emphasis
3601 state.pos += pos - start;
3602 if (!silent) { state.pending += state.src.slice(start, pos); }
3603 return true;
3604 }
3605
3606 state.pos = start + 2;
3607 stack = 1;
3608
3609 while (state.pos + 1 < max) {
3610 if (state.src.charCodeAt(state.pos) === 0x2B/* + */) {
3611 if (state.src.charCodeAt(state.pos + 1) === 0x2B/* + */) {
3612 lastChar = state.src.charCodeAt(state.pos - 1);
3613 nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1;
3614 if (nextChar !== 0x2B/* + */ && lastChar !== 0x2B/* + */) {
3615 if (lastChar !== 0x20 && lastChar !== 0x0A) {
3616 // closing '++'
3617 stack--;
3618 } else if (nextChar !== 0x20 && nextChar !== 0x0A) {
3619 // opening '++'
3620 stack++;
3621 } // else {
3622 // // standalone ' ++ ' indented with spaces
3623 // }
3624 if (stack <= 0) {
3625 found = true;
3626 break;
3627 }
3628 }
3629 }
3630 }
3631
3632 state.parser.skipToken(state);
3633 }
3634
3635 if (!found) {
3636 // parser failed to find ending tag, so it's not valid emphasis
3637 state.pos = start;
3638 return false;
3639 }
3640
3641 // found!
3642 state.posMax = state.pos;
3643 state.pos = start + 2;
3644
3645 if (!silent) {
3646 state.push({ type: 'ins_open', level: state.level++ });
3647 state.parser.tokenize(state);
3648 state.push({ type: 'ins_close', level: --state.level });
3649 }
3650
3651 state.pos = state.posMax + 2;
3652 state.posMax = max;
3653 return true;
3654}
3655
3656// Process ==highlighted text==
3657
3658function mark(state, silent) {
3659 var found,
3660 pos,
3661 stack,
3662 max = state.posMax,
3663 start = state.pos,
3664 lastChar,
3665 nextChar;
3666
3667 if (state.src.charCodeAt(start) !== 0x3D/* = */) { return false; }
3668 if (silent) { return false; } // don't run any pairs in validation mode
3669 if (start + 4 >= max) { return false; }
3670 if (state.src.charCodeAt(start + 1) !== 0x3D/* = */) { return false; }
3671 if (state.level >= state.options.maxNesting) { return false; }
3672
3673 lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;
3674 nextChar = state.src.charCodeAt(start + 2);
3675
3676 if (lastChar === 0x3D/* = */) { return false; }
3677 if (nextChar === 0x3D/* = */) { return false; }
3678 if (nextChar === 0x20 || nextChar === 0x0A) { return false; }
3679
3680 pos = start + 2;
3681 while (pos < max && state.src.charCodeAt(pos) === 0x3D/* = */) { pos++; }
3682 if (pos !== start + 2) {
3683 // sequence of 3+ markers taking as literal, same as in a emphasis
3684 state.pos += pos - start;
3685 if (!silent) { state.pending += state.src.slice(start, pos); }
3686 return true;
3687 }
3688
3689 state.pos = start + 2;
3690 stack = 1;
3691
3692 while (state.pos + 1 < max) {
3693 if (state.src.charCodeAt(state.pos) === 0x3D/* = */) {
3694 if (state.src.charCodeAt(state.pos + 1) === 0x3D/* = */) {
3695 lastChar = state.src.charCodeAt(state.pos - 1);
3696 nextChar = state.pos + 2 < max ? state.src.charCodeAt(state.pos + 2) : -1;
3697 if (nextChar !== 0x3D/* = */ && lastChar !== 0x3D/* = */) {
3698 if (lastChar !== 0x20 && lastChar !== 0x0A) {
3699 // closing '=='
3700 stack--;
3701 } else if (nextChar !== 0x20 && nextChar !== 0x0A) {
3702 // opening '=='
3703 stack++;
3704 } // else {
3705 // // standalone ' == ' indented with spaces
3706 // }
3707 if (stack <= 0) {
3708 found = true;
3709 break;
3710 }
3711 }
3712 }
3713 }
3714
3715 state.parser.skipToken(state);
3716 }
3717
3718 if (!found) {
3719 // parser failed to find ending tag, so it's not valid emphasis
3720 state.pos = start;
3721 return false;
3722 }
3723
3724 // found!
3725 state.posMax = state.pos;
3726 state.pos = start + 2;
3727
3728 if (!silent) {
3729 state.push({ type: 'mark_open', level: state.level++ });
3730 state.parser.tokenize(state);
3731 state.push({ type: 'mark_close', level: --state.level });
3732 }
3733
3734 state.pos = state.posMax + 2;
3735 state.posMax = max;
3736 return true;
3737}
3738
3739// Process *this* and _that_
3740
3741function isAlphaNum(code) {
3742 return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) ||
3743 (code >= 0x41 /* A */ && code <= 0x5A /* Z */) ||
3744 (code >= 0x61 /* a */ && code <= 0x7A /* z */);
3745}
3746
3747// parse sequence of emphasis markers,
3748// "start" should point at a valid marker
3749function scanDelims(state, start) {
3750 var pos = start, lastChar, nextChar, count,
3751 can_open = true,
3752 can_close = true,
3753 max = state.posMax,
3754 marker = state.src.charCodeAt(start);
3755
3756 lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;
3757
3758 while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }
3759 if (pos >= max) { can_open = false; }
3760 count = pos - start;
3761
3762 if (count >= 4) {
3763 // sequence of four or more unescaped markers can't start/end an emphasis
3764 can_open = can_close = false;
3765 } else {
3766 nextChar = pos < max ? state.src.charCodeAt(pos) : -1;
3767
3768 // check whitespace conditions
3769 if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; }
3770 if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; }
3771
3772 if (marker === 0x5F /* _ */) {
3773 // check if we aren't inside the word
3774 if (isAlphaNum(lastChar)) { can_open = false; }
3775 if (isAlphaNum(nextChar)) { can_close = false; }
3776 }
3777 }
3778
3779 return {
3780 can_open: can_open,
3781 can_close: can_close,
3782 delims: count
3783 };
3784}
3785
3786function emphasis(state, silent) {
3787 var startCount,
3788 count,
3789 found,
3790 oldCount,
3791 newCount,
3792 stack,
3793 res,
3794 max = state.posMax,
3795 start = state.pos,
3796 marker = state.src.charCodeAt(start);
3797
3798 if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; }
3799 if (silent) { return false; } // don't run any pairs in validation mode
3800
3801 res = scanDelims(state, start);
3802 startCount = res.delims;
3803 if (!res.can_open) {
3804 state.pos += startCount;
3805 if (!silent) { state.pending += state.src.slice(start, state.pos); }
3806 return true;
3807 }
3808
3809 if (state.level >= state.options.maxNesting) { return false; }
3810
3811 state.pos = start + startCount;
3812 stack = [ startCount ];
3813
3814 while (state.pos < max) {
3815 if (state.src.charCodeAt(state.pos) === marker) {
3816 res = scanDelims(state, state.pos);
3817 count = res.delims;
3818 if (res.can_close) {
3819 oldCount = stack.pop();
3820 newCount = count;
3821
3822 while (oldCount !== newCount) {
3823 if (newCount < oldCount) {
3824 stack.push(oldCount - newCount);
3825 break;
3826 }
3827
3828 // assert(newCount > oldCount)
3829 newCount -= oldCount;
3830
3831 if (stack.length === 0) { break; }
3832 state.pos += oldCount;
3833 oldCount = stack.pop();
3834 }
3835
3836 if (stack.length === 0) {
3837 startCount = oldCount;
3838 found = true;
3839 break;
3840 }
3841 state.pos += count;
3842 continue;
3843 }
3844
3845 if (res.can_open) { stack.push(count); }
3846 state.pos += count;
3847 continue;
3848 }
3849
3850 state.parser.skipToken(state);
3851 }
3852
3853 if (!found) {
3854 // parser failed to find ending tag, so it's not valid emphasis
3855 state.pos = start;
3856 return false;
3857 }
3858
3859 // found!
3860 state.posMax = state.pos;
3861 state.pos = start + startCount;
3862
3863 if (!silent) {
3864 if (startCount === 2 || startCount === 3) {
3865 state.push({ type: 'strong_open', level: state.level++ });
3866 }
3867 if (startCount === 1 || startCount === 3) {
3868 state.push({ type: 'em_open', level: state.level++ });
3869 }
3870
3871 state.parser.tokenize(state);
3872
3873 if (startCount === 1 || startCount === 3) {
3874 state.push({ type: 'em_close', level: --state.level });
3875 }
3876 if (startCount === 2 || startCount === 3) {
3877 state.push({ type: 'strong_close', level: --state.level });
3878 }
3879 }
3880
3881 state.pos = state.posMax + startCount;
3882 state.posMax = max;
3883 return true;
3884}
3885
3886// Process ~subscript~
3887
3888// same as UNESCAPE_MD_RE plus a space
3889var UNESCAPE_RE = /\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
3890
3891function sub(state, silent) {
3892 var found,
3893 content,
3894 max = state.posMax,
3895 start = state.pos;
3896
3897 if (state.src.charCodeAt(start) !== 0x7E/* ~ */) { return false; }
3898 if (silent) { return false; } // don't run any pairs in validation mode
3899 if (start + 2 >= max) { return false; }
3900 if (state.level >= state.options.maxNesting) { return false; }
3901
3902 state.pos = start + 1;
3903
3904 while (state.pos < max) {
3905 if (state.src.charCodeAt(state.pos) === 0x7E/* ~ */) {
3906 found = true;
3907 break;
3908 }
3909
3910 state.parser.skipToken(state);
3911 }
3912
3913 if (!found || start + 1 === state.pos) {
3914 state.pos = start;
3915 return false;
3916 }
3917
3918 content = state.src.slice(start + 1, state.pos);
3919
3920 // don't allow unescaped spaces/newlines inside
3921 if (content.match(/(^|[^\\])(\\\\)*\s/)) {
3922 state.pos = start;
3923 return false;
3924 }
3925
3926 // found!
3927 state.posMax = state.pos;
3928 state.pos = start + 1;
3929
3930 if (!silent) {
3931 state.push({
3932 type: 'sub',
3933 level: state.level,
3934 content: content.replace(UNESCAPE_RE, '$1')
3935 });
3936 }
3937
3938 state.pos = state.posMax + 1;
3939 state.posMax = max;
3940 return true;
3941}
3942
3943// Process ^superscript^
3944
3945// same as UNESCAPE_MD_RE plus a space
3946var UNESCAPE_RE$1 = /\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
3947
3948function sup(state, silent) {
3949 var found,
3950 content,
3951 max = state.posMax,
3952 start = state.pos;
3953
3954 if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; }
3955 if (silent) { return false; } // don't run any pairs in validation mode
3956 if (start + 2 >= max) { return false; }
3957 if (state.level >= state.options.maxNesting) { return false; }
3958
3959 state.pos = start + 1;
3960
3961 while (state.pos < max) {
3962 if (state.src.charCodeAt(state.pos) === 0x5E/* ^ */) {
3963 found = true;
3964 break;
3965 }
3966
3967 state.parser.skipToken(state);
3968 }
3969
3970 if (!found || start + 1 === state.pos) {
3971 state.pos = start;
3972 return false;
3973 }
3974
3975 content = state.src.slice(start + 1, state.pos);
3976
3977 // don't allow unescaped spaces/newlines inside
3978 if (content.match(/(^|[^\\])(\\\\)*\s/)) {
3979 state.pos = start;
3980 return false;
3981 }
3982
3983 // found!
3984 state.posMax = state.pos;
3985 state.pos = start + 1;
3986
3987 if (!silent) {
3988 state.push({
3989 type: 'sup',
3990 level: state.level,
3991 content: content.replace(UNESCAPE_RE$1, '$1')
3992 });
3993 }
3994
3995 state.pos = state.posMax + 1;
3996 state.posMax = max;
3997 return true;
3998}
3999
4000// Process [links](<to> "stuff")
4001
4002
4003function links(state, silent) {
4004 var labelStart,
4005 labelEnd,
4006 label,
4007 href,
4008 title,
4009 pos,
4010 ref,
4011 code,
4012 isImage = false,
4013 oldPos = state.pos,
4014 max = state.posMax,
4015 start = state.pos,
4016 marker = state.src.charCodeAt(start);
4017
4018 if (marker === 0x21/* ! */) {
4019 isImage = true;
4020 marker = state.src.charCodeAt(++start);
4021 }
4022
4023 if (marker !== 0x5B/* [ */) { return false; }
4024 if (state.level >= state.options.maxNesting) { return false; }
4025
4026 labelStart = start + 1;
4027 labelEnd = parseLinkLabel(state, start);
4028
4029 // parser failed to find ']', so it's not a valid link
4030 if (labelEnd < 0) { return false; }
4031
4032 pos = labelEnd + 1;
4033 if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
4034 //
4035 // Inline link
4036 //
4037
4038 // [link]( <href> "title" )
4039 // ^^ skipping these spaces
4040 pos++;
4041 for (; pos < max; pos++) {
4042 code = state.src.charCodeAt(pos);
4043 if (code !== 0x20 && code !== 0x0A) { break; }
4044 }
4045 if (pos >= max) { return false; }
4046
4047 // [link]( <href> "title" )
4048 // ^^^^^^ parsing link destination
4049 start = pos;
4050 if (parseLinkDestination(state, pos)) {
4051 href = state.linkContent;
4052 pos = state.pos;
4053 } else {
4054 href = '';
4055 }
4056
4057 // [link]( <href> "title" )
4058 // ^^ skipping these spaces
4059 start = pos;
4060 for (; pos < max; pos++) {
4061 code = state.src.charCodeAt(pos);
4062 if (code !== 0x20 && code !== 0x0A) { break; }
4063 }
4064
4065 // [link]( <href> "title" )
4066 // ^^^^^^^ parsing link title
4067 if (pos < max && start !== pos && parseLinkTitle(state, pos)) {
4068 title = state.linkContent;
4069 pos = state.pos;
4070
4071 // [link]( <href> "title" )
4072 // ^^ skipping these spaces
4073 for (; pos < max; pos++) {
4074 code = state.src.charCodeAt(pos);
4075 if (code !== 0x20 && code !== 0x0A) { break; }
4076 }
4077 } else {
4078 title = '';
4079 }
4080
4081 if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
4082 state.pos = oldPos;
4083 return false;
4084 }
4085 pos++;
4086 } else {
4087 //
4088 // Link reference
4089 //
4090
4091 // do not allow nested reference links
4092 if (state.linkLevel > 0) { return false; }
4093
4094 // [foo] [bar]
4095 // ^^ optional whitespace (can include newlines)
4096 for (; pos < max; pos++) {
4097 code = state.src.charCodeAt(pos);
4098 if (code !== 0x20 && code !== 0x0A) { break; }
4099 }
4100
4101 if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
4102 start = pos + 1;
4103 pos = parseLinkLabel(state, pos);
4104 if (pos >= 0) {
4105 label = state.src.slice(start, pos++);
4106 } else {
4107 pos = start - 1;
4108 }
4109 }
4110
4111 // covers label === '' and label === undefined
4112 // (collapsed reference link and shortcut reference link respectively)
4113 if (!label) {
4114 if (typeof label === 'undefined') {
4115 pos = labelEnd + 1;
4116 }
4117 label = state.src.slice(labelStart, labelEnd);
4118 }
4119
4120 ref = state.env.references[normalizeReference(label)];
4121 if (!ref) {
4122 state.pos = oldPos;
4123 return false;
4124 }
4125 href = ref.href;
4126 title = ref.title;
4127 }
4128
4129 //
4130 // We found the end of the link, and know for a fact it's a valid link;
4131 // so all that's left to do is to call tokenizer.
4132 //
4133 if (!silent) {
4134 state.pos = labelStart;
4135 state.posMax = labelEnd;
4136
4137 if (isImage) {
4138 state.push({
4139 type: 'image',
4140 src: href,
4141 title: title,
4142 alt: state.src.substr(labelStart, labelEnd - labelStart),
4143 level: state.level
4144 });
4145 } else {
4146 state.push({
4147 type: 'link_open',
4148 href: href,
4149 title: title,
4150 level: state.level++
4151 });
4152 state.linkLevel++;
4153 state.parser.tokenize(state);
4154 state.linkLevel--;
4155 state.push({ type: 'link_close', level: --state.level });
4156 }
4157 }
4158
4159 state.pos = pos;
4160 state.posMax = max;
4161 return true;
4162}
4163
4164// Process inline footnotes (^[...])
4165
4166
4167function footnote_inline(state, silent) {
4168 var labelStart,
4169 labelEnd,
4170 footnoteId,
4171 oldLength,
4172 max = state.posMax,
4173 start = state.pos;
4174
4175 if (start + 2 >= max) { return false; }
4176 if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; }
4177 if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) { return false; }
4178 if (state.level >= state.options.maxNesting) { return false; }
4179
4180 labelStart = start + 2;
4181 labelEnd = parseLinkLabel(state, start + 1);
4182
4183 // parser failed to find ']', so it's not a valid note
4184 if (labelEnd < 0) { return false; }
4185
4186 // We found the end of the link, and know for a fact it's a valid link;
4187 // so all that's left to do is to call tokenizer.
4188 //
4189 if (!silent) {
4190 if (!state.env.footnotes) { state.env.footnotes = {}; }
4191 if (!state.env.footnotes.list) { state.env.footnotes.list = []; }
4192 footnoteId = state.env.footnotes.list.length;
4193
4194 state.pos = labelStart;
4195 state.posMax = labelEnd;
4196
4197 state.push({
4198 type: 'footnote_ref',
4199 id: footnoteId,
4200 level: state.level
4201 });
4202 state.linkLevel++;
4203 oldLength = state.tokens.length;
4204 state.parser.tokenize(state);
4205 state.env.footnotes.list[footnoteId] = { tokens: state.tokens.splice(oldLength) };
4206 state.linkLevel--;
4207 }
4208
4209 state.pos = labelEnd + 1;
4210 state.posMax = max;
4211 return true;
4212}
4213
4214// Process footnote references ([^...])
4215
4216function footnote_ref(state, silent) {
4217 var label,
4218 pos,
4219 footnoteId,
4220 footnoteSubId,
4221 max = state.posMax,
4222 start = state.pos;
4223
4224 // should be at least 4 chars - "[^x]"
4225 if (start + 3 > max) { return false; }
4226
4227 if (!state.env.footnotes || !state.env.footnotes.refs) { return false; }
4228 if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; }
4229 if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; }
4230 if (state.level >= state.options.maxNesting) { return false; }
4231
4232 for (pos = start + 2; pos < max; pos++) {
4233 if (state.src.charCodeAt(pos) === 0x20) { return false; }
4234 if (state.src.charCodeAt(pos) === 0x0A) { return false; }
4235 if (state.src.charCodeAt(pos) === 0x5D /* ] */) {
4236 break;
4237 }
4238 }
4239
4240 if (pos === start + 2) { return false; } // no empty footnote labels
4241 if (pos >= max) { return false; }
4242 pos++;
4243
4244 label = state.src.slice(start + 2, pos - 1);
4245 if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; }
4246
4247 if (!silent) {
4248 if (!state.env.footnotes.list) { state.env.footnotes.list = []; }
4249
4250 if (state.env.footnotes.refs[':' + label] < 0) {
4251 footnoteId = state.env.footnotes.list.length;
4252 state.env.footnotes.list[footnoteId] = { label: label, count: 0 };
4253 state.env.footnotes.refs[':' + label] = footnoteId;
4254 } else {
4255 footnoteId = state.env.footnotes.refs[':' + label];
4256 }
4257
4258 footnoteSubId = state.env.footnotes.list[footnoteId].count;
4259 state.env.footnotes.list[footnoteId].count++;
4260
4261 state.push({
4262 type: 'footnote_ref',
4263 id: footnoteId,
4264 subId: footnoteSubId,
4265 level: state.level
4266 });
4267 }
4268
4269 state.pos = pos;
4270 state.posMax = max;
4271 return true;
4272}
4273
4274// List of valid url schemas, accorting to commonmark spec
4275// http://jgm.github.io/CommonMark/spec.html#autolinks
4276
4277var url_schemas = [
4278 'coap',
4279 'doi',
4280 'javascript',
4281 'aaa',
4282 'aaas',
4283 'about',
4284 'acap',
4285 'cap',
4286 'cid',
4287 'crid',
4288 'data',
4289 'dav',
4290 'dict',
4291 'dns',
4292 'file',
4293 'ftp',
4294 'geo',
4295 'go',
4296 'gopher',
4297 'h323',
4298 'http',
4299 'https',
4300 'iax',
4301 'icap',
4302 'im',
4303 'imap',
4304 'info',
4305 'ipp',
4306 'iris',
4307 'iris.beep',
4308 'iris.xpc',
4309 'iris.xpcs',
4310 'iris.lwz',
4311 'ldap',
4312 'mailto',
4313 'mid',
4314 'msrp',
4315 'msrps',
4316 'mtqp',
4317 'mupdate',
4318 'news',
4319 'nfs',
4320 'ni',
4321 'nih',
4322 'nntp',
4323 'opaquelocktoken',
4324 'pop',
4325 'pres',
4326 'rtsp',
4327 'service',
4328 'session',
4329 'shttp',
4330 'sieve',
4331 'sip',
4332 'sips',
4333 'sms',
4334 'snmp',
4335 'soap.beep',
4336 'soap.beeps',
4337 'tag',
4338 'tel',
4339 'telnet',
4340 'tftp',
4341 'thismessage',
4342 'tn3270',
4343 'tip',
4344 'tv',
4345 'urn',
4346 'vemmi',
4347 'ws',
4348 'wss',
4349 'xcon',
4350 'xcon-userid',
4351 'xmlrpc.beep',
4352 'xmlrpc.beeps',
4353 'xmpp',
4354 'z39.50r',
4355 'z39.50s',
4356 'adiumxtra',
4357 'afp',
4358 'afs',
4359 'aim',
4360 'apt',
4361 'attachment',
4362 'aw',
4363 'beshare',
4364 'bitcoin',
4365 'bolo',
4366 'callto',
4367 'chrome',
4368 'chrome-extension',
4369 'com-eventbrite-attendee',
4370 'content',
4371 'cvs',
4372 'dlna-playsingle',
4373 'dlna-playcontainer',
4374 'dtn',
4375 'dvb',
4376 'ed2k',
4377 'facetime',
4378 'feed',
4379 'finger',
4380 'fish',
4381 'gg',
4382 'git',
4383 'gizmoproject',
4384 'gtalk',
4385 'hcp',
4386 'icon',
4387 'ipn',
4388 'irc',
4389 'irc6',
4390 'ircs',
4391 'itms',
4392 'jar',
4393 'jms',
4394 'keyparc',
4395 'lastfm',
4396 'ldaps',
4397 'magnet',
4398 'maps',
4399 'market',
4400 'message',
4401 'mms',
4402 'ms-help',
4403 'msnim',
4404 'mumble',
4405 'mvn',
4406 'notes',
4407 'oid',
4408 'palm',
4409 'paparazzi',
4410 'platform',
4411 'proxy',
4412 'psyc',
4413 'query',
4414 'res',
4415 'resource',
4416 'rmi',
4417 'rsync',
4418 'rtmp',
4419 'secondlife',
4420 'sftp',
4421 'sgn',
4422 'skype',
4423 'smb',
4424 'soldat',
4425 'spotify',
4426 'ssh',
4427 'steam',
4428 'svn',
4429 'teamspeak',
4430 'things',
4431 'udp',
4432 'unreal',
4433 'ut2004',
4434 'ventrilo',
4435 'view-source',
4436 'webcal',
4437 'wtai',
4438 'wyciwyg',
4439 'xfire',
4440 'xri',
4441 'ymsgr'
4442];
4443
4444// Process autolinks '<protocol:...>'
4445
4446
4447/*eslint max-len:0*/
4448var EMAIL_RE = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/;
4449var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;
4450
4451
4452function autolink(state, silent) {
4453 var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos;
4454
4455 if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }
4456
4457 tail = state.src.slice(pos);
4458
4459 if (tail.indexOf('>') < 0) { return false; }
4460
4461 linkMatch = tail.match(AUTOLINK_RE);
4462
4463 if (linkMatch) {
4464 if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; }
4465
4466 url = linkMatch[0].slice(1, -1);
4467 fullUrl = normalizeLink(url);
4468 if (!state.parser.validateLink(url)) { return false; }
4469
4470 if (!silent) {
4471 state.push({
4472 type: 'link_open',
4473 href: fullUrl,
4474 level: state.level
4475 });
4476 state.push({
4477 type: 'text',
4478 content: url,
4479 level: state.level + 1
4480 });
4481 state.push({ type: 'link_close', level: state.level });
4482 }
4483
4484 state.pos += linkMatch[0].length;
4485 return true;
4486 }
4487
4488 emailMatch = tail.match(EMAIL_RE);
4489
4490 if (emailMatch) {
4491
4492 url = emailMatch[0].slice(1, -1);
4493
4494 fullUrl = normalizeLink('mailto:' + url);
4495 if (!state.parser.validateLink(fullUrl)) { return false; }
4496
4497 if (!silent) {
4498 state.push({
4499 type: 'link_open',
4500 href: fullUrl,
4501 level: state.level
4502 });
4503 state.push({
4504 type: 'text',
4505 content: url,
4506 level: state.level + 1
4507 });
4508 state.push({ type: 'link_close', level: state.level });
4509 }
4510
4511 state.pos += emailMatch[0].length;
4512 return true;
4513 }
4514
4515 return false;
4516}
4517
4518// Regexps to match html elements
4519
4520function replace$1(regex, options) {
4521 regex = regex.source;
4522 options = options || '';
4523
4524 return function self(name, val) {
4525 if (!name) {
4526 return new RegExp(regex, options);
4527 }
4528 val = val.source || val;
4529 regex = regex.replace(name, val);
4530 return self;
4531 };
4532}
4533
4534
4535var attr_name = /[a-zA-Z_:][a-zA-Z0-9:._-]*/;
4536
4537var unquoted = /[^"'=<>`\x00-\x20]+/;
4538var single_quoted = /'[^']*'/;
4539var double_quoted = /"[^"]*"/;
4540
4541/*eslint no-spaced-func:0*/
4542var attr_value = replace$1(/(?:unquoted|single_quoted|double_quoted)/)
4543 ('unquoted', unquoted)
4544 ('single_quoted', single_quoted)
4545 ('double_quoted', double_quoted)
4546 ();
4547
4548var attribute = replace$1(/(?:\s+attr_name(?:\s*=\s*attr_value)?)/)
4549 ('attr_name', attr_name)
4550 ('attr_value', attr_value)
4551 ();
4552
4553var open_tag = replace$1(/<[A-Za-z][A-Za-z0-9]*attribute*\s*\/?>/)
4554 ('attribute', attribute)
4555 ();
4556
4557var close_tag = /<\/[A-Za-z][A-Za-z0-9]*\s*>/;
4558var comment = /<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->/;
4559var processing = /<[?].*?[?]>/;
4560var declaration = /<![A-Z]+\s+[^>]*>/;
4561var cdata = /<!\[CDATA\[[\s\S]*?\]\]>/;
4562
4563var HTML_TAG_RE = replace$1(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/)
4564 ('open_tag', open_tag)
4565 ('close_tag', close_tag)
4566 ('comment', comment)
4567 ('processing', processing)
4568 ('declaration', declaration)
4569 ('cdata', cdata)
4570 ();
4571
4572// Process html tags
4573
4574
4575function isLetter$2(ch) {
4576 /*eslint no-bitwise:0*/
4577 var lc = ch | 0x20; // to lower case
4578 return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */);
4579}
4580
4581
4582function htmltag(state, silent) {
4583 var ch, match, max, pos = state.pos;
4584
4585 if (!state.options.html) { return false; }
4586
4587 // Check start
4588 max = state.posMax;
4589 if (state.src.charCodeAt(pos) !== 0x3C/* < */ ||
4590 pos + 2 >= max) {
4591 return false;
4592 }
4593
4594 // Quick fail on second char
4595 ch = state.src.charCodeAt(pos + 1);
4596 if (ch !== 0x21/* ! */ &&
4597 ch !== 0x3F/* ? */ &&
4598 ch !== 0x2F/* / */ &&
4599 !isLetter$2(ch)) {
4600 return false;
4601 }
4602
4603 match = state.src.slice(pos).match(HTML_TAG_RE);
4604 if (!match) { return false; }
4605
4606 if (!silent) {
4607 state.push({
4608 type: 'htmltag',
4609 content: state.src.slice(pos, pos + match[0].length),
4610 level: state.level
4611 });
4612 }
4613 state.pos += match[0].length;
4614 return true;
4615}
4616
4617// Process html entity - &#123;, &#xAF;, &quot;, ...
4618
4619
4620var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i;
4621var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i;
4622
4623
4624function entity(state, silent) {
4625 var ch, code, match, pos = state.pos, max = state.posMax;
4626
4627 if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; }
4628
4629 if (pos + 1 < max) {
4630 ch = state.src.charCodeAt(pos + 1);
4631
4632 if (ch === 0x23 /* # */) {
4633 match = state.src.slice(pos).match(DIGITAL_RE);
4634 if (match) {
4635 if (!silent) {
4636 code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10);
4637 state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD);
4638 }
4639 state.pos += match[0].length;
4640 return true;
4641 }
4642 } else {
4643 match = state.src.slice(pos).match(NAMED_RE);
4644 if (match) {
4645 var decoded = decodeEntity(match[1]);
4646 if (match[1] !== decoded) {
4647 if (!silent) { state.pending += decoded; }
4648 state.pos += match[0].length;
4649 return true;
4650 }
4651 }
4652 }
4653 }
4654
4655 if (!silent) { state.pending += '&'; }
4656 state.pos++;
4657 return true;
4658}
4659
4660/**
4661 * Inline Parser `rules`
4662 */
4663
4664var _rules$2 = [
4665 [ 'text', text ],
4666 [ 'newline', newline ],
4667 [ 'escape', escape ],
4668 [ 'backticks', backticks ],
4669 [ 'del', del ],
4670 [ 'ins', ins ],
4671 [ 'mark', mark ],
4672 [ 'emphasis', emphasis ],
4673 [ 'sub', sub ],
4674 [ 'sup', sup ],
4675 [ 'links', links ],
4676 [ 'footnote_inline', footnote_inline ],
4677 [ 'footnote_ref', footnote_ref ],
4678 [ 'autolink', autolink ],
4679 [ 'htmltag', htmltag ],
4680 [ 'entity', entity ]
4681];
4682
4683/**
4684 * Inline Parser class. Note that link validation is stricter
4685 * in Remarkable than what is specified by CommonMark. If you
4686 * want to change this you can use a custom validator.
4687 *
4688 * @api private
4689 */
4690
4691function ParserInline() {
4692 this.ruler = new Ruler();
4693 for (var i = 0; i < _rules$2.length; i++) {
4694 this.ruler.push(_rules$2[i][0], _rules$2[i][1]);
4695 }
4696
4697 // Can be overridden with a custom validator
4698 this.validateLink = validateLink;
4699}
4700
4701/**
4702 * Skip a single token by running all rules in validation mode.
4703 * Returns `true` if any rule reports success.
4704 *
4705 * @param {Object} `state`
4706 * @api privage
4707 */
4708
4709ParserInline.prototype.skipToken = function (state) {
4710 var rules = this.ruler.getRules('');
4711 var len = rules.length;
4712 var pos = state.pos;
4713 var i, cached_pos;
4714
4715 if ((cached_pos = state.cacheGet(pos)) > 0) {
4716 state.pos = cached_pos;
4717 return;
4718 }
4719
4720 for (i = 0; i < len; i++) {
4721 if (rules[i](state, true)) {
4722 state.cacheSet(pos, state.pos);
4723 return;
4724 }
4725 }
4726
4727 state.pos++;
4728 state.cacheSet(pos, state.pos);
4729};
4730
4731/**
4732 * Generate tokens for the given input range.
4733 *
4734 * @param {Object} `state`
4735 * @api private
4736 */
4737
4738ParserInline.prototype.tokenize = function (state) {
4739 var rules = this.ruler.getRules('');
4740 var len = rules.length;
4741 var end = state.posMax;
4742 var ok, i;
4743
4744 while (state.pos < end) {
4745
4746 // Try all possible rules.
4747 // On success, the rule should:
4748 //
4749 // - update `state.pos`
4750 // - update `state.tokens`
4751 // - return true
4752 for (i = 0; i < len; i++) {
4753 ok = rules[i](state, false);
4754
4755 if (ok) {
4756 break;
4757 }
4758 }
4759
4760 if (ok) {
4761 if (state.pos >= end) { break; }
4762 continue;
4763 }
4764
4765 state.pending += state.src[state.pos++];
4766 }
4767
4768 if (state.pending) {
4769 state.pushPending();
4770 }
4771};
4772
4773/**
4774 * Parse the given input string.
4775 *
4776 * @param {String} `str`
4777 * @param {Object} `options`
4778 * @param {Object} `env`
4779 * @param {Array} `outTokens`
4780 * @api private
4781 */
4782
4783ParserInline.prototype.parse = function (str, options, env, outTokens) {
4784 var state = new StateInline(str, this, options, env, outTokens);
4785 this.tokenize(state);
4786};
4787
4788/**
4789 * Validate the given `url` by checking for bad protocols.
4790 *
4791 * @param {String} `url`
4792 * @return {Boolean}
4793 */
4794
4795function validateLink(url) {
4796 var BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file', 'data' ];
4797 var str = url.trim().toLowerCase();
4798 // Care about digital entities "javascript&#x3A;alert(1)"
4799 str = replaceEntities(str);
4800 if (str.indexOf(':') !== -1 && BAD_PROTOCOLS.indexOf(str.split(':')[0]) !== -1) {
4801 return false;
4802 }
4803 return true;
4804}
4805
4806// Remarkable default options
4807
4808var defaultConfig = {
4809 options: {
4810 html: false, // Enable HTML tags in source
4811 xhtmlOut: false, // Use '/' to close single tags (<br />)
4812 breaks: false, // Convert '\n' in paragraphs into <br>
4813 langPrefix: 'language-', // CSS language prefix for fenced blocks
4814 linkTarget: '', // set target to open link in
4815
4816 // Enable some language-neutral replacements + quotes beautification
4817 typographer: false,
4818
4819 // Double + single quotes replacement pairs, when typographer enabled,
4820 // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.
4821 quotes: '“”‘’',
4822
4823 // Highlighter function. Should return escaped HTML,
4824 // or '' if input not changed
4825 //
4826 // function (/*str, lang*/) { return ''; }
4827 //
4828 highlight: null,
4829
4830 maxNesting: 20 // Internal protection, recursion limit
4831 },
4832
4833 components: {
4834
4835 core: {
4836 rules: [
4837 'block',
4838 'inline',
4839 'references',
4840 'replacements',
4841 'smartquotes',
4842 'references',
4843 'abbr2',
4844 'footnote_tail'
4845 ]
4846 },
4847
4848 block: {
4849 rules: [
4850 'blockquote',
4851 'code',
4852 'fences',
4853 'footnote',
4854 'heading',
4855 'hr',
4856 'htmlblock',
4857 'lheading',
4858 'list',
4859 'paragraph',
4860 'table'
4861 ]
4862 },
4863
4864 inline: {
4865 rules: [
4866 'autolink',
4867 'backticks',
4868 'del',
4869 'emphasis',
4870 'entity',
4871 'escape',
4872 'footnote_ref',
4873 'htmltag',
4874 'links',
4875 'newline',
4876 'text'
4877 ]
4878 }
4879 }
4880};
4881
4882// Remarkable default options
4883
4884var fullConfig = {
4885 options: {
4886 html: false, // Enable HTML tags in source
4887 xhtmlOut: false, // Use '/' to close single tags (<br />)
4888 breaks: false, // Convert '\n' in paragraphs into <br>
4889 langPrefix: 'language-', // CSS language prefix for fenced blocks
4890 linkTarget: '', // set target to open link in
4891
4892 // Enable some language-neutral replacements + quotes beautification
4893 typographer: false,
4894
4895 // Double + single quotes replacement pairs, when typographer enabled,
4896 // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.
4897 quotes: '“”‘’',
4898
4899 // Highlighter function. Should return escaped HTML,
4900 // or '' if input not changed
4901 //
4902 // function (/*str, lang*/) { return ''; }
4903 //
4904 highlight: null,
4905
4906 maxNesting: 20 // Internal protection, recursion limit
4907 },
4908
4909 components: {
4910 // Don't restrict core/block/inline rules
4911 core: {},
4912 block: {},
4913 inline: {}
4914 }
4915};
4916
4917// Commonmark default options
4918
4919var commonmarkConfig = {
4920 options: {
4921 html: true, // Enable HTML tags in source
4922 xhtmlOut: true, // Use '/' to close single tags (<br />)
4923 breaks: false, // Convert '\n' in paragraphs into <br>
4924 langPrefix: 'language-', // CSS language prefix for fenced blocks
4925 linkTarget: '', // set target to open link in
4926
4927 // Enable some language-neutral replacements + quotes beautification
4928 typographer: false,
4929
4930 // Double + single quotes replacement pairs, when typographer enabled,
4931 // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.
4932 quotes: '“”‘’',
4933
4934 // Highlighter function. Should return escaped HTML,
4935 // or '' if input not changed
4936 //
4937 // function (/*str, lang*/) { return ''; }
4938 //
4939 highlight: null,
4940
4941 maxNesting: 20 // Internal protection, recursion limit
4942 },
4943
4944 components: {
4945
4946 core: {
4947 rules: [
4948 'block',
4949 'inline',
4950 'references',
4951 'abbr2'
4952 ]
4953 },
4954
4955 block: {
4956 rules: [
4957 'blockquote',
4958 'code',
4959 'fences',
4960 'heading',
4961 'hr',
4962 'htmlblock',
4963 'lheading',
4964 'list',
4965 'paragraph'
4966 ]
4967 },
4968
4969 inline: {
4970 rules: [
4971 'autolink',
4972 'backticks',
4973 'emphasis',
4974 'entity',
4975 'escape',
4976 'htmltag',
4977 'links',
4978 'newline',
4979 'text'
4980 ]
4981 }
4982 }
4983};
4984
4985/**
4986 * Preset configs
4987 */
4988
4989var config = {
4990 'default': defaultConfig,
4991 'full': fullConfig,
4992 'commonmark': commonmarkConfig
4993};
4994
4995/**
4996 * The `StateCore` class manages state.
4997 *
4998 * @param {Object} `instance` Remarkable instance
4999 * @param {String} `str` Markdown string
5000 * @param {Object} `env`
5001 */
5002
5003function StateCore(instance, str, env) {
5004 this.src = str;
5005 this.env = env;
5006 this.options = instance.options;
5007 this.tokens = [];
5008 this.inlineMode = false;
5009
5010 this.inline = instance.inline;
5011 this.block = instance.block;
5012 this.renderer = instance.renderer;
5013 this.typographer = instance.typographer;
5014}
5015
5016/**
5017 * The main `Remarkable` class. Create an instance of
5018 * `Remarkable` with a `preset` and/or `options`.
5019 *
5020 * @param {String} `preset` If no preset is given, `default` is used.
5021 * @param {Object} `options`
5022 */
5023
5024function Remarkable(preset, options) {
5025 if (typeof preset !== 'string') {
5026 options = preset;
5027 preset = 'default';
5028 }
5029
5030 if (options && options.linkify != null) {
5031 console.warn(
5032 'linkify option is removed. Use linkify plugin instead:\n\n' +
5033 'import Remarkable from \'remarkable\';\n' +
5034 'import linkify from \'remarkable/linkify\';\n' +
5035 'new Remarkable().use(linkify)\n'
5036 );
5037 }
5038
5039 this.inline = new ParserInline();
5040 this.block = new ParserBlock();
5041 this.core = new Core();
5042 this.renderer = new Renderer();
5043 this.ruler = new Ruler();
5044
5045 this.options = {};
5046 this.configure(config[preset]);
5047 this.set(options || {});
5048}
5049
5050/**
5051 * Set options as an alternative to passing them
5052 * to the constructor.
5053 *
5054 * ```js
5055 * md.set({typographer: true});
5056 * ```
5057 * @param {Object} `options`
5058 * @api public
5059 */
5060
5061Remarkable.prototype.set = function (options) {
5062 assign(this.options, options);
5063};
5064
5065/**
5066 * Batch loader for components rules states, and options
5067 *
5068 * @param {Object} `presets`
5069 */
5070
5071Remarkable.prototype.configure = function (presets) {
5072 var self = this;
5073
5074 if (!presets) { throw new Error('Wrong `remarkable` preset, check name/content'); }
5075 if (presets.options) { self.set(presets.options); }
5076 if (presets.components) {
5077 Object.keys(presets.components).forEach(function (name) {
5078 if (presets.components[name].rules) {
5079 self[name].ruler.enable(presets.components[name].rules, true);
5080 }
5081 });
5082 }
5083};
5084
5085/**
5086 * Use a plugin.
5087 *
5088 * ```js
5089 * var md = new Remarkable();
5090 *
5091 * md.use(plugin1)
5092 * .use(plugin2, opts)
5093 * .use(plugin3);
5094 * ```
5095 *
5096 * @param {Function} `plugin`
5097 * @param {Object} `options`
5098 * @return {Object} `Remarkable` for chaining
5099 */
5100
5101Remarkable.prototype.use = function (plugin, options) {
5102 plugin(this, options);
5103 return this;
5104};
5105
5106
5107/**
5108 * Parse the input `string` and return a tokens array.
5109 * Modifies `env` with definitions data.
5110 *
5111 * @param {String} `string`
5112 * @param {Object} `env`
5113 * @return {Array} Array of tokens
5114 */
5115
5116Remarkable.prototype.parse = function (str, env) {
5117 var state = new StateCore(this, str, env);
5118 this.core.process(state);
5119 return state.tokens;
5120};
5121
5122/**
5123 * The main `.render()` method that does all the magic :)
5124 *
5125 * @param {String} `string`
5126 * @param {Object} `env`
5127 * @return {String} Rendered HTML.
5128 */
5129
5130Remarkable.prototype.render = function (str, env) {
5131 env = env || {};
5132 return this.renderer.render(this.parse(str, env), this.options, env);
5133};
5134
5135/**
5136 * Parse the given content `string` as a single string.
5137 *
5138 * @param {String} `string`
5139 * @param {Object} `env`
5140 * @return {Array} Array of tokens
5141 */
5142
5143Remarkable.prototype.parseInline = function (str, env) {
5144 var state = new StateCore(this, str, env);
5145 state.inlineMode = true;
5146 this.core.process(state);
5147 return state.tokens;
5148};
5149
5150/**
5151 * Render a single content `string`, without wrapping it
5152 * to paragraphs
5153 *
5154 * @param {String} `str`
5155 * @param {Object} `env`
5156 * @return {String}
5157 */
5158
5159Remarkable.prototype.renderInline = function (str, env) {
5160 env = env || {};
5161 return this.renderer.render(this.parseInline(str, env), this.options, env);
5162};
5163
5164export { Remarkable, utils };
Note: See TracBrowser for help on using the repository browser.