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 | ;
|
12 |
|
13 | /* eslint-env commonjs */
|
14 |
|
15 | /*
|
16 | * Dependencies.
|
17 | */
|
18 |
|
19 | var he = require('he');
|
20 | var table = require('markdown-table');
|
21 | var repeat = require('repeat-string');
|
22 | var extend = require('extend.js');
|
23 | var ccount = require('ccount');
|
24 | var longestStreak = require('longest-streak');
|
25 | var utilities = require('./utilities.js');
|
26 | var defaultOptions = require('./defaults.js').stringify;
|
27 |
|
28 | /*
|
29 | * Methods.
|
30 | */
|
31 |
|
32 | var raise = utilities.raise;
|
33 | var validate = utilities.validate;
|
34 |
|
35 | /*
|
36 | * Constants.
|
37 | */
|
38 |
|
39 | var INDENT = 4;
|
40 | var MINIMUM_CODE_FENCE_LENGTH = 3;
|
41 | var YAML_FENCE_LENGTH = 3;
|
42 | var MINIMUM_RULE_LENGTH = 3;
|
43 | var MAILTO = 'mailto:';
|
44 | var ERROR_LIST_ITEM_INDENT = 'Cannot indent code properly. See ' +
|
45 | 'http://git.io/mdast-lii';
|
46 |
|
47 | /*
|
48 | * Expressions.
|
49 | */
|
50 |
|
51 | var EXPRESSIONS_WHITE_SPACE = /\s/;
|
52 |
|
53 | /*
|
54 | * Naive fence expression.
|
55 | */
|
56 |
|
57 | var FENCE = /([`~])\1{2}/;
|
58 |
|
59 | /*
|
60 | * Expression for a protocol.
|
61 | *
|
62 | * @see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
|
63 | */
|
64 |
|
65 | var PROTOCOL = /^[a-z][a-z+.-]+:\/?/i;
|
66 |
|
67 | /*
|
68 | * Characters.
|
69 | */
|
70 |
|
71 | var ANGLE_BRACKET_CLOSE = '>';
|
72 | var ANGLE_BRACKET_OPEN = '<';
|
73 | var ASTERISK = '*';
|
74 | var CARET = '^';
|
75 | var COLON = ':';
|
76 | var DASH = '-';
|
77 | var DOT = '.';
|
78 | var EMPTY = '';
|
79 | var EQUALS = '=';
|
80 | var EXCLAMATION_MARK = '!';
|
81 | var HASH = '#';
|
82 | var LINE = '\n';
|
83 | var PARENTHESIS_OPEN = '(';
|
84 | var PARENTHESIS_CLOSE = ')';
|
85 | var PIPE = '|';
|
86 | var PLUS = '+';
|
87 | var QUOTE_DOUBLE = '"';
|
88 | var QUOTE_SINGLE = '\'';
|
89 | var SPACE = ' ';
|
90 | var SQUARE_BRACKET_OPEN = '[';
|
91 | var SQUARE_BRACKET_CLOSE = ']';
|
92 | var TICK = '`';
|
93 | var TILDE = '~';
|
94 | var UNDERSCORE = '_';
|
95 |
|
96 | /*
|
97 | * Character combinations.
|
98 | */
|
99 |
|
100 | var BREAK = LINE + LINE;
|
101 | var GAP = BREAK + LINE;
|
102 | var DOUBLE_TILDE = TILDE + TILDE;
|
103 |
|
104 | /*
|
105 | * Allowed entity options.
|
106 | */
|
107 |
|
108 | var ENTITY_OPTIONS = {};
|
109 |
|
110 | ENTITY_OPTIONS.true = true;
|
111 | ENTITY_OPTIONS.false = true;
|
112 | ENTITY_OPTIONS.numbers = true;
|
113 | ENTITY_OPTIONS.escape = true;
|
114 |
|
115 | /*
|
116 | * Allowed list-bullet characters.
|
117 | */
|
118 |
|
119 | var LIST_BULLETS = {};
|
120 |
|
121 | LIST_BULLETS[ASTERISK] = true;
|
122 | LIST_BULLETS[DASH] = true;
|
123 | LIST_BULLETS[PLUS] = true;
|
124 |
|
125 | /*
|
126 | * Allowed horizontal-rule bullet characters.
|
127 | */
|
128 |
|
129 | var HORIZONTAL_RULE_BULLETS = {};
|
130 |
|
131 | HORIZONTAL_RULE_BULLETS[ASTERISK] = true;
|
132 | HORIZONTAL_RULE_BULLETS[DASH] = true;
|
133 | HORIZONTAL_RULE_BULLETS[UNDERSCORE] = true;
|
134 |
|
135 | /*
|
136 | * Allowed emphasis characters.
|
137 | */
|
138 |
|
139 | var EMPHASIS_MARKERS = {};
|
140 |
|
141 | EMPHASIS_MARKERS[UNDERSCORE] = true;
|
142 | EMPHASIS_MARKERS[ASTERISK] = true;
|
143 |
|
144 | /*
|
145 | * Allowed fence markers.
|
146 | */
|
147 |
|
148 | var FENCE_MARKERS = {};
|
149 |
|
150 | FENCE_MARKERS[TICK] = true;
|
151 | FENCE_MARKERS[TILDE] = true;
|
152 |
|
153 | /*
|
154 | * Which method to use based on `list.ordered`.
|
155 | */
|
156 |
|
157 | var ORDERED_MAP = {};
|
158 |
|
159 | ORDERED_MAP.true = 'visitOrderedItems';
|
160 | ORDERED_MAP.false = 'visitUnorderedItems';
|
161 |
|
162 | /*
|
163 | * Allowed list-item-indent's.
|
164 | */
|
165 |
|
166 | var LIST_ITEM_INDENTS = {};
|
167 |
|
168 | var LIST_ITEM_TAB = 'tab';
|
169 | var LIST_ITEM_ONE = '1';
|
170 | var LIST_ITEM_MIXED = 'mixed';
|
171 |
|
172 | LIST_ITEM_INDENTS[LIST_ITEM_ONE] = true;
|
173 | LIST_ITEM_INDENTS[LIST_ITEM_TAB] = true;
|
174 | LIST_ITEM_INDENTS[LIST_ITEM_MIXED] = true;
|
175 |
|
176 | /*
|
177 | * Which checkbox to use.
|
178 | */
|
179 |
|
180 | var CHECKBOX_MAP = {};
|
181 |
|
182 | CHECKBOX_MAP.null = EMPTY;
|
183 | CHECKBOX_MAP.undefined = EMPTY;
|
184 | CHECKBOX_MAP.true = SQUARE_BRACKET_OPEN + 'x' + SQUARE_BRACKET_CLOSE + SPACE;
|
185 | CHECKBOX_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 | */
|
199 | function 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&T'
|
227 | *
|
228 | * encode = encodeFactory('numbers', file);
|
229 | * encode('AT&T') // 'ATT&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 | */
|
237 | function 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&T'
|
259 | *
|
260 | * // When `type` is `'numbers'`.
|
261 | * encode('AT&T'); // 'ATT&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 | */
|
307 | function 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 | */
|
339 | function 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 | */
|
360 | function 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 | */
|
390 | function 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 |
|
404 | var compilerPrototype = Compiler.prototype;
|
405 |
|
406 | /*
|
407 | * Expose defaults.
|
408 | */
|
409 |
|
410 | compilerPrototype.options = defaultOptions;
|
411 |
|
412 | /*
|
413 | * Map of applicable enum's.
|
414 | */
|
415 |
|
416 | var 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 | */
|
439 | compilerPrototype.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 | */
|
491 | compilerPrototype.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 | */
|
531 | compilerPrototype.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 | */
|
584 | compilerPrototype.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 | */
|
630 | compilerPrototype.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 | */
|
671 | compilerPrototype.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 | */
|
738 | compilerPrototype.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 | */
|
779 | compilerPrototype.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&T
|
808 | *
|
809 | * Supports numbered entities in `settings.encode: numbers`
|
810 | * mode:
|
811 | *
|
812 | * AT&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 | */
|
826 | compilerPrototype.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 | */
|
845 | compilerPrototype.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 | */
|
870 | compilerPrototype.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 | */
|
898 | compilerPrototype.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 | */
|
927 | compilerPrototype.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 | */
|
975 | compilerPrototype.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 | */
|
1038 | compilerPrototype.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 | */
|
1070 | compilerPrototype.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 | */
|
1123 | compilerPrototype.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 | */
|
1187 | compilerPrototype.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 | */
|
1218 | compilerPrototype.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 | */
|
1253 | compilerPrototype.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 | */
|
1285 | compilerPrototype.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 | */
|
1304 | compilerPrototype.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 | */
|
1326 | compilerPrototype.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 | */
|
1364 | compilerPrototype.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 | */
|
1425 | function 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 | */
|
1462 | compilerPrototype.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 | */
|
1490 | compilerPrototype.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 | */
|
1513 | compilerPrototype.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 | */
|
1540 | compilerPrototype.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 | */
|
1576 | compilerPrototype.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 | */
|
1612 | compilerPrototype.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 | */
|
1639 | compilerPrototype.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 | */
|
1720 | compilerPrototype.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 | */
|
1761 | compilerPrototype.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 | */
|
1785 | compilerPrototype.compile = function () {
|
1786 | return this.visit(this.file.namespace('mdast').tree);
|
1787 | };
|
1788 |
|
1789 | /*
|
1790 | * Expose `stringify` on `module.exports`.
|
1791 | */
|
1792 |
|
1793 | module.exports = Compiler;
|