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