UNPKG

39.2 kBJavaScriptView Raw
1/**
2 * @author Titus Wormer
3 * @copyright 2015 Titus Wormer
4 * @license MIT
5 * @module mdast:stringify
6 * @version 2.2.2
7 * @fileoverview Compile an abstract syntax tree into
8 * a markdown document.
9 */
10
11'use strict';
12
13/* eslint-env commonjs */
14
15/*
16 * Dependencies.
17 */
18
19var he = require('he');
20var table = require('markdown-table');
21var repeat = require('repeat-string');
22var extend = require('extend.js');
23var ccount = require('ccount');
24var longestStreak = require('longest-streak');
25var utilities = require('./utilities.js');
26var defaultOptions = require('./defaults.js').stringify;
27
28/*
29 * Methods.
30 */
31
32var raise = utilities.raise;
33var validate = utilities.validate;
34
35/*
36 * Constants.
37 */
38
39var INDENT = 4;
40var MINIMUM_CODE_FENCE_LENGTH = 3;
41var YAML_FENCE_LENGTH = 3;
42var MINIMUM_RULE_LENGTH = 3;
43var MAILTO = 'mailto:';
44var ERROR_LIST_ITEM_INDENT = 'Cannot indent code properly. See ' +
45 'http://git.io/mdast-lii';
46
47/*
48 * Expressions.
49 */
50
51var EXPRESSIONS_WHITE_SPACE = /\s/;
52
53/*
54 * Naive fence expression.
55 */
56
57var FENCE = /([`~])\1{2}/;
58
59/*
60 * Expression for a protocol.
61 *
62 * @see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
63 */
64
65var PROTOCOL = /^[a-z][a-z+.-]+:\/?/i;
66
67/*
68 * Characters.
69 */
70
71var ANGLE_BRACKET_CLOSE = '>';
72var ANGLE_BRACKET_OPEN = '<';
73var ASTERISK = '*';
74var CARET = '^';
75var COLON = ':';
76var DASH = '-';
77var DOT = '.';
78var EMPTY = '';
79var EQUALS = '=';
80var EXCLAMATION_MARK = '!';
81var HASH = '#';
82var LINE = '\n';
83var PARENTHESIS_OPEN = '(';
84var PARENTHESIS_CLOSE = ')';
85var PIPE = '|';
86var PLUS = '+';
87var QUOTE_DOUBLE = '"';
88var QUOTE_SINGLE = '\'';
89var SPACE = ' ';
90var SQUARE_BRACKET_OPEN = '[';
91var SQUARE_BRACKET_CLOSE = ']';
92var TICK = '`';
93var TILDE = '~';
94var UNDERSCORE = '_';
95
96/*
97 * Character combinations.
98 */
99
100var BREAK = LINE + LINE;
101var GAP = BREAK + LINE;
102var DOUBLE_TILDE = TILDE + TILDE;
103
104/*
105 * Allowed entity options.
106 */
107
108var ENTITY_OPTIONS = {};
109
110ENTITY_OPTIONS.true = true;
111ENTITY_OPTIONS.false = true;
112ENTITY_OPTIONS.numbers = true;
113ENTITY_OPTIONS.escape = true;
114
115/*
116 * Allowed list-bullet characters.
117 */
118
119var LIST_BULLETS = {};
120
121LIST_BULLETS[ASTERISK] = true;
122LIST_BULLETS[DASH] = true;
123LIST_BULLETS[PLUS] = true;
124
125/*
126 * Allowed horizontal-rule bullet characters.
127 */
128
129var HORIZONTAL_RULE_BULLETS = {};
130
131HORIZONTAL_RULE_BULLETS[ASTERISK] = true;
132HORIZONTAL_RULE_BULLETS[DASH] = true;
133HORIZONTAL_RULE_BULLETS[UNDERSCORE] = true;
134
135/*
136 * Allowed emphasis characters.
137 */
138
139var EMPHASIS_MARKERS = {};
140
141EMPHASIS_MARKERS[UNDERSCORE] = true;
142EMPHASIS_MARKERS[ASTERISK] = true;
143
144/*
145 * Allowed fence markers.
146 */
147
148var FENCE_MARKERS = {};
149
150FENCE_MARKERS[TICK] = true;
151FENCE_MARKERS[TILDE] = true;
152
153/*
154 * Which method to use based on `list.ordered`.
155 */
156
157var ORDERED_MAP = {};
158
159ORDERED_MAP.true = 'visitOrderedItems';
160ORDERED_MAP.false = 'visitUnorderedItems';
161
162/*
163 * Allowed list-item-indent's.
164 */
165
166var LIST_ITEM_INDENTS = {};
167
168var LIST_ITEM_TAB = 'tab';
169var LIST_ITEM_ONE = '1';
170var LIST_ITEM_MIXED = 'mixed';
171
172LIST_ITEM_INDENTS[LIST_ITEM_ONE] = true;
173LIST_ITEM_INDENTS[LIST_ITEM_TAB] = true;
174LIST_ITEM_INDENTS[LIST_ITEM_MIXED] = true;
175
176/*
177 * Which checkbox to use.
178 */
179
180var CHECKBOX_MAP = {};
181
182CHECKBOX_MAP.null = EMPTY;
183CHECKBOX_MAP.undefined = EMPTY;
184CHECKBOX_MAP.true = SQUARE_BRACKET_OPEN + 'x' + SQUARE_BRACKET_CLOSE + SPACE;
185CHECKBOX_MAP.false = SQUARE_BRACKET_OPEN + SPACE + SQUARE_BRACKET_CLOSE +
186 SPACE;
187
188/**
189 * Encode noop.
190 * Simply returns the given value.
191 *
192 * @example
193 * var encode = encodeNoop();
194 * encode('AT&T') // 'AT&T'
195 *
196 * @param {string} value - Content.
197 * @return {string} - Content, without any modifications.
198 */
199function encodeNoop(value) {
200 return value;
201}
202
203/**
204 * Factory to encode HTML entities.
205 * Creates a no-operation function when `type` is
206 * `'false'`, a function which encodes using named
207 * references when `type` is `'true'`, and a function
208 * which encodes using numbered references when `type` is
209 * `'numbers'`.
210 *
211 * By default this should not throw errors, but he does
212 * throw an error when in `strict` mode:
213 *
214 * he.encode.options.strict = true;
215 * encodeFactory('true')('\x01') // throws
216 *
217 * These are thrown on the currently compiled `File`.
218 *
219 * @example
220 * var file = new File();
221 *
222 * var encode = encodeFactory('false', file);
223 * encode('AT&T') // 'AT&T'
224 *
225 * encode = encodeFactory('true', file);
226 * encode('AT&T') // 'AT&amp;T'
227 *
228 * encode = encodeFactory('numbers', file);
229 * encode('AT&T') // 'ATT&#x26;T'
230 *
231 * @param {string} type - Either `'true'`, `'false'`, or
232 * `numbers`.
233 * @param {File} file - Currently compiled virtual file.
234 * @return {function(string): string} - Function which
235 * takes a value and returns its encoded version.
236 */
237function encodeFactory(type, file) {
238 var options = {};
239 var fn;
240
241 if (type === 'false') {
242 return encodeNoop;
243 }
244
245 if (type === 'true') {
246 options.useNamedReferences = true;
247 }
248
249 fn = type === 'escape' ? 'escape' : 'encode';
250
251 /**
252 * Encode HTML entities using `he` using bound options.
253 *
254 * @see https://github.com/mathiasbynens/he#strict
255 *
256 * @example
257 * // When `type` is `'true'`.
258 * encode('AT&T'); // 'AT&amp;T'
259 *
260 * // When `type` is `'numbers'`.
261 * encode('AT&T'); // 'ATT&#x26;T'
262 *
263 * @param {string} value - Content.
264 * @param {Object} [node] - Node which is compiled.
265 * @return {string} - Encoded content.
266 * @throws {Error} - When `file.quiet` is not `true`.
267 * However, by default `he` does not throw on
268 * parse errors, but when
269 * `he.encode.options.strict: true`, they occur on
270 * invalid HTML.
271 */
272 function encode(value, node) {
273 /* istanbul ignore next - useful for other stringifiers */
274 var position = node ? node.position : null;
275 try {
276 return he[fn](value, options);
277 } catch (exception) {
278 file.fail(exception, position);
279 }
280 }
281
282 return encode;
283}
284
285/**
286 * Wrap `url` in angle brackets when needed, or when
287 * forced.
288 *
289 * In links, images, and definitions, the URL part needs
290 * to be enclosed when it:
291 *
292 * - has a length of `0`;
293 * - contains white-space;
294 * - has more or less opening than closing parentheses.
295 *
296 * @example
297 * encloseURI('foo bar') // '<foo bar>'
298 * encloseURI('foo(bar(baz)') // '<foo(bar(baz)>'
299 * encloseURI('') // '<>'
300 * encloseURI('example.com') // 'example.com'
301 * encloseURI('example.com', true) // '<example.com>'
302 *
303 * @param {string} uri
304 * @param {boolean?} [always] - Force enclosing.
305 * @return {boolean} - Properly enclosed `uri`.
306 */
307function encloseURI(uri, always) {
308 if (
309 always ||
310 !uri.length ||
311 EXPRESSIONS_WHITE_SPACE.test(uri) ||
312 ccount(uri, PARENTHESIS_OPEN) !== ccount(uri, PARENTHESIS_CLOSE)
313 ) {
314 return ANGLE_BRACKET_OPEN + uri + ANGLE_BRACKET_CLOSE;
315 }
316
317 return uri;
318}
319
320/**
321 * There is currently no way to support nested delimiters
322 * across Markdown.pl, CommonMark, and GitHub (RedCarpet).
323 * The following code supports Markdown.pl and GitHub.
324 * CommonMark is not supported when mixing double- and
325 * single quotes inside a title.
326 *
327 * @see https://github.com/vmg/redcarpet/issues/473
328 * @see https://github.com/jgm/CommonMark/issues/308
329 *
330 * @example
331 * encloseTitle('foo') // '"foo"'
332 * encloseTitle('foo \'bar\' baz') // '"foo \'bar\' baz"'
333 * encloseTitle('foo "bar" baz') // '\'foo "bar" baz\''
334 * encloseTitle('foo "bar" \'baz\'') // '"foo "bar" \'baz\'"'
335 *
336 * @param {string} title - Content.
337 * @return {string} - Properly enclosed title.
338 */
339function encloseTitle(title) {
340 var delimiter = QUOTE_DOUBLE;
341
342 if (title.indexOf(delimiter) !== -1) {
343 delimiter = QUOTE_SINGLE;
344 }
345
346 return delimiter + title + delimiter;
347}
348
349/**
350 * Pad `value` with `level * INDENT` spaces. Respects
351 * lines. Ignores empty lines.
352 *
353 * @example
354 * pad('foo', 1) // ' foo'
355 *
356 * @param {string} value - Content.
357 * @param {number} level - Indentation level.
358 * @return {string} - Padded `value`.
359 */
360function pad(value, level) {
361 var index;
362 var padding;
363
364 value = value.split(LINE);
365
366 index = value.length;
367 padding = repeat(SPACE, level * INDENT);
368
369 while (index--) {
370 if (value[index].length !== 0) {
371 value[index] = padding + value[index];
372 }
373 }
374
375 return value.join(LINE);
376}
377
378/**
379 * Construct a new compiler.
380 *
381 * @example
382 * var compiler = new Compiler(new File('> foo.'));
383 *
384 * @constructor
385 * @class {Compiler}
386 * @param {File} file - Virtual file.
387 * @param {Object?} [options] - Passed to
388 * `Compiler#setOptions()`.
389 */
390function Compiler(file, options) {
391 var self = this;
392
393 self.file = file;
394
395 self.options = extend({}, self.options);
396
397 self.setOptions(options);
398}
399
400/*
401 * Cache prototype.
402 */
403
404var compilerPrototype = Compiler.prototype;
405
406/*
407 * Expose defaults.
408 */
409
410compilerPrototype.options = defaultOptions;
411
412/*
413 * Map of applicable enum's.
414 */
415
416var maps = {
417 'entities': ENTITY_OPTIONS,
418 'bullet': LIST_BULLETS,
419 'rule': HORIZONTAL_RULE_BULLETS,
420 'listItemIndent': LIST_ITEM_INDENTS,
421 'emphasis': EMPHASIS_MARKERS,
422 'strong': EMPHASIS_MARKERS,
423 'fence': FENCE_MARKERS
424};
425
426/**
427 * Set options. Does not overwrite previously set
428 * options.
429 *
430 * @example
431 * var compiler = new Compiler();
432 * compiler.setOptions({bullet: '*'});
433 *
434 * @this {Compiler}
435 * @throws {Error} - When an option is invalid.
436 * @param {Object?} [options] - Stringify settings.
437 * @return {Compiler} - `self`.
438 */
439compilerPrototype.setOptions = function (options) {
440 var self = this;
441 var current = self.options;
442 var ruleRepetition;
443 var key;
444
445 if (options === null || options === undefined) {
446 options = {};
447 } else if (typeof options === 'object') {
448 options = extend({}, options);
449 } else {
450 raise(options, 'options');
451 }
452
453 for (key in defaultOptions) {
454 validate[typeof current[key]](
455 options, key, current[key], maps[key]
456 );
457 }
458
459 ruleRepetition = options.ruleRepetition;
460
461 if (ruleRepetition && ruleRepetition < MINIMUM_RULE_LENGTH) {
462 raise(ruleRepetition, 'options.ruleRepetition');
463 }
464
465 self.encode = encodeFactory(String(options.entities), self.file);
466
467 self.options = options;
468
469 return self;
470};
471
472/**
473 * Visit a node.
474 *
475 * @example
476 * var compiler = new Compiler();
477 *
478 * compiler.visit({
479 * type: 'strong',
480 * children: [{
481 * type: 'text',
482 * value: 'Foo'
483 * }]
484 * });
485 * // '**Foo**'
486 *
487 * @param {Object} node - Node.
488 * @param {Object?} [parent] - `node`s parent.
489 * @return {string} - Compiled `node`.
490 */
491compilerPrototype.visit = function (node, parent) {
492 var self = this;
493
494 /*
495 * Fail on unknown nodes.
496 */
497
498 if (typeof self[node.type] !== 'function') {
499 self.file.fail(
500 'Missing compiler for node of type `' +
501 node.type + '`: `' + node + '`',
502 node
503 );
504 }
505
506 return self[node.type](node, parent);
507};
508
509/**
510 * Visit all children of `parent`.
511 *
512 * @example
513 * var compiler = new Compiler();
514 *
515 * compiler.all({
516 * type: 'strong',
517 * children: [{
518 * type: 'text',
519 * value: 'Foo'
520 * },
521 * {
522 * type: 'text',
523 * value: 'Bar'
524 * }]
525 * });
526 * // ['Foo', 'Bar']
527 *
528 * @param {Object} parent - Parent node of children.
529 * @return {Array.<string>} - List of compiled children.
530 */
531compilerPrototype.all = function (parent) {
532 var self = this;
533 var children = parent.children;
534 var values = [];
535 var index = -1;
536 var length = children.length;
537
538 while (++index < length) {
539 values[index] = self.visit(children[index], parent);
540 }
541
542 return values;
543};
544
545/**
546 * Visit ordered list items.
547 *
548 * Starts the list with
549 * `node.start` and increments each following list item
550 * bullet by one:
551 *
552 * 2. foo
553 * 3. bar
554 *
555 * In `incrementListMarker: false` mode, does not increment
556 * each marker and stays on `node.start`:
557 *
558 * 1. foo
559 * 1. bar
560 *
561 * Adds an extra line after an item if it has
562 * `loose: true`.
563 *
564 * @example
565 * var compiler = new Compiler();
566 *
567 * compiler.visitOrderedItems({
568 * type: 'list',
569 * ordered: true,
570 * children: [{
571 * type: 'listItem',
572 * children: [{
573 * type: 'text',
574 * value: 'bar'
575 * }]
576 * }]
577 * });
578 * // '1. bar'
579 *
580 * @param {Object} node - `list` node with
581 * `ordered: true`.
582 * @return {string} - Markdown list.
583 */
584compilerPrototype.visitOrderedItems = function (node) {
585 var self = this;
586 var increment = self.options.incrementListMarker;
587 var values = [];
588 var start = node.start;
589 var children = node.children;
590 var length = children.length;
591 var index = -1;
592 var bullet;
593
594 while (++index < length) {
595 bullet = (increment ? start + index : start) + DOT;
596 values[index] = self.listItem(children[index], node, index, bullet);
597 }
598
599 return values.join(LINE);
600};
601
602/**
603 * Visit unordered list items.
604 *
605 * Uses `options.bullet` as each item's bullet.
606 *
607 * Adds an extra line after an item if it has
608 * `loose: true`.
609 *
610 * @example
611 * var compiler = new Compiler();
612 *
613 * compiler.visitUnorderedItems({
614 * type: 'list',
615 * ordered: false,
616 * children: [{
617 * type: 'listItem',
618 * children: [{
619 * type: 'text',
620 * value: 'bar'
621 * }]
622 * }]
623 * });
624 * // '- bar'
625 *
626 * @param {Object} node - `list` node with
627 * `ordered: false`.
628 * @return {string} - Markdown list.
629 */
630compilerPrototype.visitUnorderedItems = function (node) {
631 var self = this;
632 var values = [];
633 var children = node.children;
634 var length = children.length;
635 var index = -1;
636 var bullet = self.options.bullet;
637
638 while (++index < length) {
639 values[index] = self.listItem(children[index], node, index, bullet);
640 }
641
642 return values.join(LINE);
643};
644
645/**
646 * Stringify a block node with block children (e.g., `root`
647 * or `blockquote`).
648 *
649 * Knows about code following a list, or adjacent lists
650 * with similar bullets, and places an extra newline
651 * between them.
652 *
653 * @example
654 * var compiler = new Compiler();
655 *
656 * compiler.block({
657 * type: 'root',
658 * children: [{
659 * type: 'paragraph',
660 * children: [{
661 * type: 'text',
662 * value: 'bar'
663 * }]
664 * }]
665 * });
666 * // 'bar'
667 *
668 * @param {Object} node - `root` node.
669 * @return {string} - Markdown block content.
670 */
671compilerPrototype.block = function (node) {
672 var self = this;
673 var values = [];
674 var children = node.children;
675 var length = children.length;
676 var index = -1;
677 var child;
678 var prev;
679
680 while (++index < length) {
681 child = children[index];
682
683 if (prev) {
684 /*
685 * Duplicate nodes, such as a list
686 * directly following another list,
687 * often need multiple new lines.
688 *
689 * Additionally, code blocks following a list
690 * might easily be mistaken for a paragraph
691 * in the list itself.
692 */
693
694 if (child.type === prev.type && prev.type === 'list') {
695 values.push(prev.ordered === child.ordered ? GAP : BREAK);
696 } else if (
697 prev.type === 'list' &&
698 child.type === 'code' &&
699 !child.lang
700 ) {
701 values.push(GAP);
702 } else {
703 values.push(BREAK);
704 }
705 }
706
707 values.push(self.visit(child, node));
708
709 prev = child;
710 }
711
712 return values.join(EMPTY);
713};
714
715/**
716 * Stringify a root.
717 *
718 * Adds a final newline to ensure valid POSIX files.
719 *
720 * @example
721 * var compiler = new Compiler();
722 *
723 * compiler.root({
724 * type: 'root',
725 * children: [{
726 * type: 'paragraph',
727 * children: [{
728 * type: 'text',
729 * value: 'bar'
730 * }]
731 * }]
732 * });
733 * // 'bar'
734 *
735 * @param {Object} node - `root` node.
736 * @return {string} - Markdown document.
737 */
738compilerPrototype.root = function (node) {
739 return this.block(node) + LINE;
740};
741
742/**
743 * Stringify a heading.
744 *
745 * In `setext: true` mode and when `depth` is smaller than
746 * three, creates a setext header:
747 *
748 * Foo
749 * ===
750 *
751 * Otherwise, an ATX header is generated:
752 *
753 * ### Foo
754 *
755 * In `closeAtx: true` mode, the header is closed with
756 * hashes:
757 *
758 * ### Foo ###
759 *
760 * @example
761 * var compiler = new Compiler();
762 *
763 * compiler.heading({
764 * type: 'heading',
765 * depth: 2,
766 * children: [{
767 * type: 'strong',
768 * children: [{
769 * type: 'text',
770 * value: 'bar'
771 * }]
772 * }]
773 * });
774 * // '## **bar**'
775 *
776 * @param {Object} node - `heading` node.
777 * @return {string} - Markdown heading.
778 */
779compilerPrototype.heading = function (node) {
780 var self = this;
781 var setext = self.options.setext;
782 var closeAtx = self.options.closeAtx;
783 var depth = node.depth;
784 var content = self.all(node).join(EMPTY);
785 var prefix;
786
787 if (setext && depth < 3) {
788 return content + LINE +
789 repeat(depth === 1 ? EQUALS : DASH, content.length);
790 }
791
792 prefix = repeat(HASH, node.depth);
793 content = prefix + SPACE + content;
794
795 if (closeAtx) {
796 content += SPACE + prefix;
797 }
798
799 return content;
800};
801
802/**
803 * Stringify text.
804 *
805 * Supports named entities in `settings.encode: true` mode:
806 *
807 * AT&amp;T
808 *
809 * Supports numbered entities in `settings.encode: numbers`
810 * mode:
811 *
812 * AT&#x26;T
813 *
814 * @example
815 * var compiler = new Compiler();
816 *
817 * compiler.text({
818 * type: 'text',
819 * value: 'foo'
820 * });
821 * // 'foo'
822 *
823 * @param {Object} node - `text` node.
824 * @return {string} - Raw markdown text.
825 */
826compilerPrototype.text = function (node) {
827 return this.encode(node.value, node);
828};
829
830/**
831 * Stringify escaped text.
832 *
833 * @example
834 * var compiler = new Compiler();
835 *
836 * compiler.escape({
837 * type: 'escape',
838 * value: '\n'
839 * });
840 * // '\\\n'
841 *
842 * @param {Object} node - `escape` node.
843 * @return {string} - Markdown escape.
844 */
845compilerPrototype.escape = function (node) {
846 return '\\' + node.value;
847};
848
849/**
850 * Stringify a paragraph.
851 *
852 * @example
853 * var compiler = new Compiler();
854 *
855 * compiler.paragraph({
856 * type: 'paragraph',
857 * children: [{
858 * type: 'strong',
859 * children: [{
860 * type: 'text',
861 * value: 'bar'
862 * }]
863 * }]
864 * });
865 * // '**bar**'
866 *
867 * @param {Object} node - `paragraph` node.
868 * @return {string} - Markdown paragraph.
869 */
870compilerPrototype.paragraph = function (node) {
871 return this.all(node).join(EMPTY);
872};
873
874/**
875 * Stringify a block quote.
876 *
877 * @example
878 * var compiler = new Compiler();
879 *
880 * compiler.paragraph({
881 * type: 'blockquote',
882 * children: [{
883 * type: 'paragraph',
884 * children: [{
885 * type: 'strong',
886 * children: [{
887 * type: 'text',
888 * value: 'bar'
889 * }]
890 * }]
891 * }]
892 * });
893 * // '> **bar**'
894 *
895 * @param {Object} node - `blockquote` node.
896 * @return {string} - Markdown block quote.
897 */
898compilerPrototype.blockquote = function (node) {
899 var indent = ANGLE_BRACKET_CLOSE + SPACE;
900
901 return indent + this.block(node).split(LINE).join(LINE + indent);
902};
903
904/**
905 * Stringify a list. See `Compiler#visitOrderedList()` and
906 * `Compiler#visitUnorderedList()` for internal working.
907 *
908 * @example
909 * var compiler = new Compiler();
910 *
911 * compiler.visitUnorderedItems({
912 * type: 'list',
913 * ordered: false,
914 * children: [{
915 * type: 'listItem',
916 * children: [{
917 * type: 'text',
918 * value: 'bar'
919 * }]
920 * }]
921 * });
922 * // '- bar'
923 *
924 * @param {Object} node - `list` node.
925 * @return {string} - Markdown list.
926 */
927compilerPrototype.list = function (node) {
928 return this[ORDERED_MAP[node.ordered]](node);
929};
930
931/**
932 * Stringify a list item.
933 *
934 * Prefixes the content with a checked checkbox when
935 * `checked: true`:
936 *
937 * [x] foo
938 *
939 * Prefixes the content with an unchecked checkbox when
940 * `checked: false`:
941 *
942 * [ ] foo
943 *
944 * @example
945 * var compiler = new Compiler();
946 *
947 * compiler.listItem({
948 * type: 'listItem',
949 * checked: true,
950 * children: [{
951 * type: 'text',
952 * value: 'bar'
953 * }]
954 * }, {
955 * type: 'list',
956 * ordered: false,
957 * children: [{
958 * type: 'listItem',
959 * checked: true,
960 * children: [{
961 * type: 'text',
962 * value: 'bar'
963 * }]
964 * }]
965 * }, 0, '*');
966 * '- [x] bar'
967 *
968 * @param {Object} node - `listItem` node.
969 * @param {Object} parent - `list` node.
970 * @param {number} position - Index of `node` in `parent`.
971 * @param {string} bullet - Bullet to use. This, and the
972 * `listItemIndent` setting define the used indent.
973 * @return {string} - Markdown list item.
974 */
975compilerPrototype.listItem = function (node, parent, position, bullet) {
976 var self = this;
977 var style = self.options.listItemIndent;
978 var children = node.children;
979 var values = [];
980 var index = -1;
981 var length = children.length;
982 var loose = node.loose;
983 var value;
984 var indent;
985 var spacing;
986
987 while (++index < length) {
988 values[index] = self.visit(children[index], node);
989 }
990
991 value = CHECKBOX_MAP[node.checked] + values.join(loose ? BREAK : LINE);
992
993 if (
994 style === LIST_ITEM_ONE ||
995 (style === LIST_ITEM_MIXED && value.indexOf(LINE) === -1)
996 ) {
997 indent = bullet.length + 1;
998 spacing = SPACE;
999 } else {
1000 indent = Math.ceil((bullet.length + 1) / INDENT) * INDENT;
1001 spacing = repeat(SPACE, indent - bullet.length);
1002 }
1003
1004 value = bullet + spacing + pad(value, indent / INDENT).slice(indent);
1005
1006 if (loose && parent.children.length - 1 !== position) {
1007 value += LINE;
1008 }
1009
1010 return value;
1011};
1012
1013/**
1014 * Stringify inline code.
1015 *
1016 * Knows about internal ticks (`\``), and ensures one more
1017 * tick is used to enclose the inline code:
1018 *
1019 * ```foo ``bar`` baz```
1020 *
1021 * Even knows about inital and final ticks:
1022 *
1023 * `` `foo ``
1024 * `` foo` ``
1025 *
1026 * @example
1027 * var compiler = new Compiler();
1028 *
1029 * compiler.inlineCode({
1030 * type: 'inlineCode',
1031 * value: 'foo(); `bar`; baz()'
1032 * });
1033 * // '``foo(); `bar`; baz()``'
1034 *
1035 * @param {Object} node - `inlineCode` node.
1036 * @return {string} - Markdown inline code.
1037 */
1038compilerPrototype.inlineCode = function (node) {
1039 var value = node.value;
1040 var ticks = repeat(TICK, longestStreak(value, TICK) + 1);
1041 var start = ticks;
1042 var end = ticks;
1043
1044 if (value.charAt(0) === TICK) {
1045 start += SPACE;
1046 }
1047
1048 if (value.charAt(value.length - 1) === TICK) {
1049 end = SPACE + end;
1050 }
1051
1052 return start + node.value + end;
1053};
1054
1055/**
1056 * Stringify YAML front matter.
1057 *
1058 * @example
1059 * var compiler = new Compiler();
1060 *
1061 * compiler.yaml({
1062 * type: 'yaml',
1063 * value: 'foo: bar'
1064 * });
1065 * // '---\nfoo: bar\n---'
1066 *
1067 * @param {Object} node - `yaml` node.
1068 * @return {string} - Markdown YAML document.
1069 */
1070compilerPrototype.yaml = function (node) {
1071 var delimiter = repeat(DASH, YAML_FENCE_LENGTH);
1072 var value = node.value ? LINE + node.value : EMPTY;
1073
1074 return delimiter + value + LINE + delimiter;
1075};
1076
1077/**
1078 * Stringify a code block.
1079 *
1080 * Creates indented code when:
1081 *
1082 * - No language tag exists;
1083 * - Not in `fences: true` mode;
1084 * - A non-empty value exists.
1085 *
1086 * Otherwise, GFM fenced code is created:
1087 *
1088 * ```js
1089 * foo();
1090 * ```
1091 *
1092 * When in ``fence: `~` `` mode, uses tildes as fences:
1093 *
1094 * ~~~js
1095 * foo();
1096 * ~~~
1097 *
1098 * Knows about internal fences (Note: GitHub/Kramdown does
1099 * not support this):
1100 *
1101 * ````javascript
1102 * ```markdown
1103 * foo
1104 * ```
1105 * ````
1106 *
1107 * Supports named entities in the language flag with
1108 * `settings.encode` mode.
1109 *
1110 * @example
1111 * var compiler = new Compiler();
1112 *
1113 * compiler.code({
1114 * type: 'code',
1115 * lang: 'js',
1116 * value: 'fooo();'
1117 * });
1118 * // '```js\nfooo();\n```'
1119 *
1120 * @param {Object} node - `code` node.
1121 * @return {string} - Markdown code block.
1122 */
1123compilerPrototype.code = function (node, parent) {
1124 var self = this;
1125 var value = node.value;
1126 var options = self.options;
1127 var marker = options.fence;
1128 var language = self.encode(node.lang || EMPTY, node);
1129 var fence;
1130
1131 /*
1132 * Without (needed) fences.
1133 */
1134
1135 if (!language && !options.fences && value) {
1136 /*
1137 * Throw when pedantic, in a list item which
1138 * isn’t compiled using a tab.
1139 */
1140
1141 if (
1142 parent &&
1143 parent.type === 'listItem' &&
1144 options.listItemIndent !== LIST_ITEM_TAB &&
1145 options.pedantic
1146 ) {
1147 self.file.fail(ERROR_LIST_ITEM_INDENT, node.position);
1148 }
1149
1150 return pad(value, 1);
1151 }
1152
1153 fence = longestStreak(value, marker) + 1;
1154
1155 /*
1156 * Fix GFM / RedCarpet bug, where fence-like characters
1157 * inside fenced code can exit a code-block.
1158 * Yes, even when the outer fence uses different
1159 * characters, or is longer.
1160 * Thus, we can only pad the code to make it work.
1161 */
1162
1163 if (FENCE.test(value)) {
1164 value = pad(value, 1);
1165 }
1166
1167 fence = repeat(marker, Math.max(fence, MINIMUM_CODE_FENCE_LENGTH));
1168
1169 return fence + language + LINE + value + LINE + fence;
1170};
1171
1172/**
1173 * Stringify HTML.
1174 *
1175 * @example
1176 * var compiler = new Compiler();
1177 *
1178 * compiler.html({
1179 * type: 'html',
1180 * value: '<div>bar</div>'
1181 * });
1182 * // '<div>bar</div>'
1183 *
1184 * @param {Object} node - `html` node.
1185 * @return {string} - Markdown HTML.
1186 */
1187compilerPrototype.html = function (node) {
1188 return node.value;
1189};
1190
1191/**
1192 * Stringify a horizontal rule.
1193 *
1194 * The character used is configurable by `rule`: (`'_'`)
1195 *
1196 * ___
1197 *
1198 * The number of repititions is defined through
1199 * `ruleRepetition`: (`6`)
1200 *
1201 * ******
1202 *
1203 * Whether spaces delimit each character, is configured
1204 * through `ruleSpaces`: (`true`)
1205 *
1206 * * * *
1207 *
1208 * @example
1209 * var compiler = new Compiler();
1210 *
1211 * compiler.horizontalRule({
1212 * type: 'horizontalRule'
1213 * });
1214 * // '***'
1215 *
1216 * @return {string} - Markdown rule.
1217 */
1218compilerPrototype.horizontalRule = function () {
1219 var options = this.options;
1220 var rule = repeat(options.rule, options.ruleRepetition);
1221
1222 if (options.ruleSpaces) {
1223 rule = rule.split(EMPTY).join(SPACE);
1224 }
1225
1226 return rule;
1227};
1228
1229/**
1230 * Stringify a strong.
1231 *
1232 * The marker used is configurable by `strong`, which
1233 * defaults to an asterisk (`'*'`) but also accepts an
1234 * underscore (`'_'`):
1235 *
1236 * _foo_
1237 *
1238 * @example
1239 * var compiler = new Compiler();
1240 *
1241 * compiler.strong({
1242 * type: 'strong',
1243 * children: [{
1244 * type: 'text',
1245 * value: 'Foo'
1246 * }]
1247 * });
1248 * // '**Foo**'
1249 *
1250 * @param {Object} node - `strong` node.
1251 * @return {string} - Markdown strong-emphasised text.
1252 */
1253compilerPrototype.strong = function (node) {
1254 var marker = this.options.strong;
1255
1256 marker = marker + marker;
1257
1258 return marker + this.all(node).join(EMPTY) + marker;
1259};
1260
1261/**
1262 * Stringify an emphasis.
1263 *
1264 * The marker used is configurable by `emphasis`, which
1265 * defaults to an underscore (`'_'`) but also accepts an
1266 * asterisk (`'*'`):
1267 *
1268 * *foo*
1269 *
1270 * @example
1271 * var compiler = new Compiler();
1272 *
1273 * compiler.emphasis({
1274 * type: 'emphasis',
1275 * children: [{
1276 * type: 'text',
1277 * value: 'Foo'
1278 * }]
1279 * });
1280 * // '_Foo_'
1281 *
1282 * @param {Object} node - `emphasis` node.
1283 * @return {string} - Markdown emphasised text.
1284 */
1285compilerPrototype.emphasis = function (node) {
1286 var marker = this.options.emphasis;
1287
1288 return marker + this.all(node).join(EMPTY) + marker;
1289};
1290
1291/**
1292 * Stringify a hard break.
1293 *
1294 * @example
1295 * var compiler = new Compiler();
1296 *
1297 * compiler.break({
1298 * type: 'break'
1299 * });
1300 * // ' \n'
1301 *
1302 * @return {string} - Hard markdown break.
1303 */
1304compilerPrototype.break = function () {
1305 return SPACE + SPACE + LINE;
1306};
1307
1308/**
1309 * Stringify a delete.
1310 *
1311 * @example
1312 * var compiler = new Compiler();
1313 *
1314 * compiler.delete({
1315 * type: 'delete',
1316 * children: [{
1317 * type: 'text',
1318 * value: 'Foo'
1319 * }]
1320 * });
1321 * // '~~Foo~~'
1322 *
1323 * @param {Object} node - `delete` node.
1324 * @return {string} - Markdown strike-through.
1325 */
1326compilerPrototype.delete = function (node) {
1327 return DOUBLE_TILDE + this.all(node).join(EMPTY) + DOUBLE_TILDE;
1328};
1329
1330/**
1331 * Stringify a link.
1332 *
1333 * When no title exists, the compiled `children` equal
1334 * `href`, and `href` starts with a protocol, an auto
1335 * link is created:
1336 *
1337 * <http://example.com>
1338 *
1339 * Otherwise, is smart about enclosing `href` (see
1340 * `encloseURI()`) and `title` (see `encloseTitle()`).
1341 *
1342 * [foo](<foo at bar dot com> 'An "example" e-mail')
1343 *
1344 * Supports named entities in the `href` and `title` when
1345 * in `settings.encode` mode.
1346 *
1347 * @example
1348 * var compiler = new Compiler();
1349 *
1350 * compiler.link({
1351 * type: 'link',
1352 * href: 'http://example.com',
1353 * title: 'Example Domain',
1354 * children: [{
1355 * type: 'text',
1356 * value: 'Foo'
1357 * }]
1358 * });
1359 * // '[Foo](http://example.com "Example Domain")'
1360 *
1361 * @param {Object} node - `link` node.
1362 * @return {string} - Markdown link.
1363 */
1364compilerPrototype.link = function (node) {
1365 var self = this;
1366 var url = self.encode(node.href, node);
1367 var value = self.all(node).join(EMPTY);
1368
1369 if (
1370 node.title === null &&
1371 PROTOCOL.test(url) &&
1372 (url === value || url === MAILTO + value)
1373 ) {
1374 return encloseURI(url, true);
1375 }
1376
1377 url = encloseURI(url);
1378
1379 if (node.title) {
1380 url += SPACE + encloseTitle(self.encode(node.title, node));
1381 }
1382
1383 value = SQUARE_BRACKET_OPEN + value + SQUARE_BRACKET_CLOSE;
1384
1385 value += PARENTHESIS_OPEN + url + PARENTHESIS_CLOSE;
1386
1387 return value;
1388};
1389
1390/**
1391 * Stringify a link label.
1392 *
1393 * Because link references are easily, mistakingly,
1394 * created (for example, `[foo]`), reference nodes have
1395 * an extra property depicting how it looked in the
1396 * original document, so stringification can cause minimal
1397 * changes.
1398 *
1399 * @example
1400 * label({
1401 * type: 'referenceImage',
1402 * referenceType: 'full',
1403 * identifier: 'foo'
1404 * });
1405 * // '[foo]'
1406 *
1407 * label({
1408 * type: 'referenceImage',
1409 * referenceType: 'collapsed',
1410 * identifier: 'foo'
1411 * });
1412 * // '[]'
1413 *
1414 * label({
1415 * type: 'referenceImage',
1416 * referenceType: 'shortcut',
1417 * identifier: 'foo'
1418 * });
1419 * // ''
1420 *
1421 * @param {Object} node - `linkReference` or
1422 * `imageReference` node.
1423 * @return {string} - Markdown label reference.
1424 */
1425function label(node) {
1426 var value = EMPTY;
1427 var type = node.referenceType;
1428
1429 if (type === 'full') {
1430 value = node.identifier;
1431 }
1432
1433 if (type !== 'shortcut') {
1434 value = SQUARE_BRACKET_OPEN + value + SQUARE_BRACKET_CLOSE;
1435 }
1436
1437 return value;
1438}
1439
1440/**
1441 * Stringify a link reference.
1442 *
1443 * See `label()` on how reference labels are created.
1444 *
1445 * @example
1446 * var compiler = new Compiler();
1447 *
1448 * compiler.linkReference({
1449 * type: 'linkReference',
1450 * referenceType: 'collapsed',
1451 * identifier: 'foo',
1452 * children: [{
1453 * type: 'text',
1454 * value: 'Foo'
1455 * }]
1456 * });
1457 * // '[Foo][]'
1458 *
1459 * @param {Object} node - `linkReference` node.
1460 * @return {string} - Markdown link reference.
1461 */
1462compilerPrototype.linkReference = function (node) {
1463 return SQUARE_BRACKET_OPEN +
1464 this.all(node).join(EMPTY) + SQUARE_BRACKET_CLOSE +
1465 label(node);
1466};
1467
1468/**
1469 * Stringify an image reference.
1470 *
1471 * See `label()` on how reference labels are created.
1472 *
1473 * Supports named entities in the `alt` when
1474 * in `settings.encode` mode.
1475 *
1476 * @example
1477 * var compiler = new Compiler();
1478 *
1479 * compiler.imageReference({
1480 * type: 'imageReference',
1481 * referenceType: 'full',
1482 * identifier: 'foo',
1483 * alt: 'Foo'
1484 * });
1485 * // '![Foo][foo]'
1486 *
1487 * @param {Object} node - `imageReference` node.
1488 * @return {string} - Markdown image reference.
1489 */
1490compilerPrototype.imageReference = function (node) {
1491 var alt = this.encode(node.alt, node);
1492
1493 return EXCLAMATION_MARK +
1494 SQUARE_BRACKET_OPEN + alt + SQUARE_BRACKET_CLOSE +
1495 label(node);
1496};
1497
1498/**
1499 * Stringify a footnote reference.
1500 *
1501 * @example
1502 * var compiler = new Compiler();
1503 *
1504 * compiler.footnoteReference({
1505 * type: 'footnoteReference',
1506 * identifier: 'foo'
1507 * });
1508 * // '[^foo]'
1509 *
1510 * @param {Object} node - `footnoteReference` node.
1511 * @return {string} - Markdown footnote reference.
1512 */
1513compilerPrototype.footnoteReference = function (node) {
1514 return SQUARE_BRACKET_OPEN + CARET + node.identifier +
1515 SQUARE_BRACKET_CLOSE;
1516};
1517
1518/**
1519 * Stringify an link- or image definition.
1520 *
1521 * Is smart about enclosing `href` (see `encloseURI()`) and
1522 * `title` (see `encloseTitle()`).
1523 *
1524 * [foo]: <foo at bar dot com> 'An "example" e-mail'
1525 *
1526 * @example
1527 * var compiler = new Compiler();
1528 *
1529 * compiler.definition({
1530 * type: 'definition',
1531 * link: 'http://example.com',
1532 * title: 'Example Domain',
1533 * identifier: 'foo'
1534 * });
1535 * // '[foo]: http://example.com "Example Domain"'
1536 *
1537 * @param {Object} node - `definition` node.
1538 * @return {string} - Markdown link- or image definition.
1539 */
1540compilerPrototype.definition = function (node) {
1541 var value = SQUARE_BRACKET_OPEN + node.identifier + SQUARE_BRACKET_CLOSE;
1542 var url = encloseURI(node.link);
1543
1544 if (node.title) {
1545 url += SPACE + encloseTitle(node.title);
1546 }
1547
1548 return value + COLON + SPACE + url;
1549};
1550
1551/**
1552 * Stringify an image.
1553 *
1554 * Is smart about enclosing `href` (see `encloseURI()`) and
1555 * `title` (see `encloseTitle()`).
1556 *
1557 * ![foo](</fav icon.png> 'My "favourite" icon')
1558 *
1559 * Supports named entities in `src`, `alt`, and `title`
1560 * when in `settings.encode` mode.
1561 *
1562 * @example
1563 * var compiler = new Compiler();
1564 *
1565 * compiler.image({
1566 * type: 'image',
1567 * href: 'http://example.png/favicon.png',
1568 * title: 'Example Icon',
1569 * alt: 'Foo'
1570 * });
1571 * // '![Foo](http://example.png/favicon.png "Example Icon")'
1572 *
1573 * @param {Object} node - `image` node.
1574 * @return {string} - Markdown image.
1575 */
1576compilerPrototype.image = function (node) {
1577 var encode = this.encode;
1578 var url = encloseURI(encode(node.src, node));
1579 var value;
1580
1581 if (node.title) {
1582 url += SPACE + encloseTitle(encode(node.title, node));
1583 }
1584
1585 value = EXCLAMATION_MARK +
1586 SQUARE_BRACKET_OPEN + encode(node.alt || EMPTY, node) +
1587 SQUARE_BRACKET_CLOSE;
1588
1589 value += PARENTHESIS_OPEN + url + PARENTHESIS_CLOSE;
1590
1591 return value;
1592};
1593
1594/**
1595 * Stringify a footnote.
1596 *
1597 * @example
1598 * var compiler = new Compiler();
1599 *
1600 * compiler.footnote({
1601 * type: 'footnote',
1602 * children: [{
1603 * type: 'text',
1604 * value: 'Foo'
1605 * }]
1606 * });
1607 * // '[^Foo]'
1608 *
1609 * @param {Object} node - `footnote` node.
1610 * @return {string} - Markdown footnote.
1611 */
1612compilerPrototype.footnote = function (node) {
1613 return SQUARE_BRACKET_OPEN + CARET + this.all(node).join(EMPTY) +
1614 SQUARE_BRACKET_CLOSE;
1615};
1616
1617/**
1618 * Stringify a footnote definition.
1619 *
1620 * @example
1621 * var compiler = new Compiler();
1622 *
1623 * compiler.footnoteDefinition({
1624 * type: 'footnoteDefinition',
1625 * identifier: 'foo',
1626 * children: [{
1627 * type: 'paragraph',
1628 * children: [{
1629 * type: 'text',
1630 * value: 'bar'
1631 * }]
1632 * }]
1633 * });
1634 * // '[^foo]: bar'
1635 *
1636 * @param {Object} node - `footnoteDefinition` node.
1637 * @return {string} - Markdown footnote definition.
1638 */
1639compilerPrototype.footnoteDefinition = function (node) {
1640 var id = node.identifier.toLowerCase();
1641
1642 return SQUARE_BRACKET_OPEN + CARET + id +
1643 SQUARE_BRACKET_CLOSE + COLON + SPACE +
1644 this.all(node).join(BREAK + repeat(SPACE, INDENT));
1645};
1646
1647/**
1648 * Stringify table.
1649 *
1650 * Creates a fenced table by default, but not in
1651 * `looseTable: true` mode:
1652 *
1653 * Foo | Bar
1654 * :-: | ---
1655 * Baz | Qux
1656 *
1657 * NOTE: Be careful with `looseTable: true` mode, as a
1658 * loose table inside an indented code block on GitHub
1659 * renders as an actual table!
1660 *
1661 * Creates a spaces table by default, but not in
1662 * `spacedTable: false`:
1663 *
1664 * |Foo|Bar|
1665 * |:-:|---|
1666 * |Baz|Qux|
1667 *
1668 * @example
1669 * var compiler = new Compiler();
1670 *
1671 * compiler.table({
1672 * type: 'table',
1673 * align: ['center', null],
1674 * children: [
1675 * {
1676 * type: 'tableHeader',
1677 * children: [
1678 * {
1679 * type: 'tableCell'
1680 * children: [{
1681 * type: 'text'
1682 * value: 'Foo'
1683 * }]
1684 * },
1685 * {
1686 * type: 'tableCell'
1687 * children: [{
1688 * type: 'text'
1689 * value: 'Bar'
1690 * }]
1691 * }
1692 * ]
1693 * },
1694 * {
1695 * type: 'tableRow',
1696 * children: [
1697 * {
1698 * type: 'tableCell'
1699 * children: [{
1700 * type: 'text'
1701 * value: 'Baz'
1702 * }]
1703 * },
1704 * {
1705 * type: 'tableCell'
1706 * children: [{
1707 * type: 'text'
1708 * value: 'Qux'
1709 * }]
1710 * }
1711 * ]
1712 * }
1713 * ]
1714 * });
1715 * // '| Foo | Bar |\n| :-: | --- |\n| Baz | Qux |'
1716 *
1717 * @param {Object} node - `table` node.
1718 * @return {string} - Markdown table.
1719 */
1720compilerPrototype.table = function (node) {
1721 var self = this;
1722 var loose = self.options.looseTable;
1723 var spaced = self.options.spacedTable;
1724 var rows = node.children;
1725 var index = rows.length;
1726 var result = [];
1727 var start;
1728
1729 while (index--) {
1730 result[index] = self.all(rows[index]);
1731 }
1732
1733 start = loose ? EMPTY : spaced ? PIPE + SPACE : PIPE;
1734
1735 return table(result, {
1736 'align': node.align,
1737 'start': start,
1738 'end': start.split(EMPTY).reverse().join(EMPTY),
1739 'delimiter': spaced ? SPACE + PIPE + SPACE : PIPE
1740 });
1741};
1742
1743/**
1744 * Stringify a table cell.
1745 *
1746 * @example
1747 * var compiler = new Compiler();
1748 *
1749 * compiler.tableCell({
1750 * type: 'tableCell',
1751 * children: [{
1752 * type: 'text'
1753 * value: 'Qux'
1754 * }]
1755 * });
1756 * // 'Qux'
1757 *
1758 * @param {Object} node - `tableCell` node.
1759 * @return {string} - Markdown table cell.
1760 */
1761compilerPrototype.tableCell = function (node) {
1762 return this.all(node).join(EMPTY);
1763};
1764
1765/**
1766 * Stringify the bound file.
1767 *
1768 * @example
1769 * var file = new VFile('__Foo__');
1770 *
1771 * file.namespace('mdast').tree = {
1772 * type: 'strong',
1773 * children: [{
1774 * type: 'text',
1775 * value: 'Foo'
1776 * }]
1777 * });
1778 *
1779 * new Compiler(file).compile();
1780 * // '**Foo**'
1781 *
1782 * @this {Compiler}
1783 * @return {string} - Markdown document.
1784 */
1785compilerPrototype.compile = function () {
1786 return this.visit(this.file.namespace('mdast').tree);
1787};
1788
1789/*
1790 * Expose `stringify` on `module.exports`.
1791 */
1792
1793module.exports = Compiler;