1 | /**
|
2 | * @author Titus Wormer
|
3 | * @copyright 2015 Titus Wormer
|
4 | * @license MIT
|
5 | * @module mdast:parse
|
6 | * @version 2.2.2
|
7 | * @fileoverview Parse a markdown document into an
|
8 | * abstract syntax tree.
|
9 | */
|
10 |
|
11 | ;
|
12 |
|
13 | /* eslint-env commonjs */
|
14 |
|
15 | /*
|
16 | * Dependencies.
|
17 | */
|
18 |
|
19 | var he = require('he');
|
20 | var repeat = require('repeat-string');
|
21 | var trim = require('trim');
|
22 | var trimTrailingLines = require('trim-trailing-lines');
|
23 | var extend = require('extend.js');
|
24 | var utilities = require('./utilities.js');
|
25 | var defaultExpressions = require('./expressions.js');
|
26 | var defaultOptions = require('./defaults.js').parse;
|
27 |
|
28 | /*
|
29 | * Methods.
|
30 | */
|
31 |
|
32 | var raise = utilities.raise;
|
33 | var clean = utilities.clean;
|
34 | var validate = utilities.validate;
|
35 | var normalize = utilities.normalizeIdentifier;
|
36 | var arrayPush = [].push;
|
37 |
|
38 | /*
|
39 | * Characters.
|
40 | */
|
41 |
|
42 | var AT_SIGN = '@';
|
43 | var CARET = '^';
|
44 | var EQUALS = '=';
|
45 | var EXCLAMATION_MARK = '!';
|
46 | var MAILTO_PROTOCOL = 'mailto:';
|
47 | var NEW_LINE = '\n';
|
48 | var SPACE = ' ';
|
49 | var TAB = '\t';
|
50 | var EMPTY = '';
|
51 | var LT = '<';
|
52 | var GT = '>';
|
53 | var BRACKET_OPEN = '[';
|
54 |
|
55 | /*
|
56 | * Types.
|
57 | */
|
58 |
|
59 | var BLOCK = 'block';
|
60 | var INLINE = 'inline';
|
61 | var HORIZONTAL_RULE = 'horizontalRule';
|
62 | var HTML = 'html';
|
63 | var YAML = 'yaml';
|
64 | var TABLE = 'table';
|
65 | var TABLE_CELL = 'tableCell';
|
66 | var TABLE_HEADER = 'tableHeader';
|
67 | var TABLE_ROW = 'tableRow';
|
68 | var PARAGRAPH = 'paragraph';
|
69 | var TEXT = 'text';
|
70 | var CODE = 'code';
|
71 | var LIST = 'list';
|
72 | var LIST_ITEM = 'listItem';
|
73 | var FOOTNOTE_DEFINITION = 'footnoteDefinition';
|
74 | var HEADING = 'heading';
|
75 | var BLOCKQUOTE = 'blockquote';
|
76 | var LINK = 'link';
|
77 | var IMAGE = 'image';
|
78 | var FOOTNOTE = 'footnote';
|
79 | var ESCAPE = 'escape';
|
80 | var STRONG = 'strong';
|
81 | var EMPHASIS = 'emphasis';
|
82 | var DELETE = 'delete';
|
83 | var INLINE_CODE = 'inlineCode';
|
84 | var BREAK = 'break';
|
85 | var ROOT = 'root';
|
86 |
|
87 | /**
|
88 | * Wrapper around he's `decode` function.
|
89 | *
|
90 | * @example
|
91 | * decode('&'); // '&'
|
92 | * decode('&'); // '&'
|
93 | *
|
94 | * @param {string} value
|
95 | * @param {function(string)} eat
|
96 | * @return {string}
|
97 | * @throws {Error} - When `eat.file.quiet` is not `true`.
|
98 | * However, by default `he` does not throw on incorrect
|
99 | * encoded entities, but when
|
100 | * `he.decode.options.strict: true`, they occur on
|
101 | * entities with a missing closing semi-colon.
|
102 | */
|
103 | function decode(value, eat) {
|
104 | try {
|
105 | return he.decode(value);
|
106 | } catch (exception) {
|
107 | eat.file.fail(exception, eat.now());
|
108 | }
|
109 | }
|
110 |
|
111 | /**
|
112 | * Factory to de-escape a value, based on an expression
|
113 | * at `key` in `scope`.
|
114 | *
|
115 | * @example
|
116 | * var expressions = {escape: /\\(a)/}
|
117 | * var descape = descapeFactory(expressions, 'escape');
|
118 | *
|
119 | * @param {Object} scope - Map of expressions.
|
120 | * @param {string} key - Key in `map` at which the
|
121 | * non-global expression exists.
|
122 | * @return {function(string): string} - Function which
|
123 | * takes a value and returns its unescaped version.
|
124 | */
|
125 | function descapeFactory(scope, key) {
|
126 | var globalExpression;
|
127 | var expression;
|
128 |
|
129 | /**
|
130 | * Private method to get a global expression
|
131 | * from the expression at `key` in `scope`.
|
132 | * This method is smart about not recreating
|
133 | * the expressions every time.
|
134 | *
|
135 | * @private
|
136 | * @return {RegExp}
|
137 | */
|
138 | function generate() {
|
139 | if (scope[key] !== globalExpression) {
|
140 | globalExpression = scope[key];
|
141 | expression = new RegExp(
|
142 | scope[key].source.replace(CARET, EMPTY), 'g'
|
143 | );
|
144 | }
|
145 |
|
146 | return expression;
|
147 | }
|
148 |
|
149 | /**
|
150 | * De-escape a string using the expression at `key`
|
151 | * in `scope`.
|
152 | *
|
153 | * @example
|
154 | * var expressions = {escape: /\\(a)/}
|
155 | * var descape = descapeFactory(expressions, 'escape');
|
156 | * descape('\a'); // 'a'
|
157 | *
|
158 | * @param {string} value - Escaped string.
|
159 | * @return {string} - Unescaped string.
|
160 | */
|
161 | function descape(value) {
|
162 | return value.replace(generate(), '$1');
|
163 | }
|
164 |
|
165 | return descape;
|
166 | }
|
167 |
|
168 | /*
|
169 | * Tab size.
|
170 | */
|
171 |
|
172 | var TAB_SIZE = 4;
|
173 |
|
174 | /*
|
175 | * Expressions.
|
176 | */
|
177 |
|
178 | var EXPRESSION_RIGHT_ALIGNMENT = /^[ \t]*-+:[ \t]*$/;
|
179 | var EXPRESSION_CENTER_ALIGNMENT = /^[ \t]*:-+:[ \t]*$/;
|
180 | var EXPRESSION_LEFT_ALIGNMENT = /^[ \t]*:-+[ \t]*$/;
|
181 | var EXPRESSION_TABLE_FENCE = /^[ \t]*|\|[ \t]*$/g;
|
182 | var EXPRESSION_TABLE_BORDER = /[ \t]*\|[ \t]*/;
|
183 | var EXPRESSION_BLOCK_QUOTE = /^[ \t]*>[ \t]?/gm;
|
184 | var EXPRESSION_BULLET = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t)([^\n]*)/;
|
185 | var EXPRESSION_PEDANTIC_BULLET = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/;
|
186 | var EXPRESSION_INITIAL_INDENT = /^( {1,4}|\t)?/gm;
|
187 | var EXPRESSION_INITIAL_TAB = /^( {4}|\t)?/gm;
|
188 | var EXPRESSION_HTML_LINK_OPEN = /^<a /i;
|
189 | var EXPRESSION_HTML_LINK_CLOSE = /^<\/a>/i;
|
190 | var EXPRESSION_LOOSE_LIST_ITEM = /\n\n(?!\s*$)/;
|
191 | var EXPRESSION_TASK_ITEM = /^\[([\ \t]|x|X)\][\ \t]/;
|
192 |
|
193 | /*
|
194 | * A map of characters, and their column length,
|
195 | * which can be used as indentation.
|
196 | */
|
197 |
|
198 | var INDENTATION_CHARACTERS = {};
|
199 |
|
200 | INDENTATION_CHARACTERS[SPACE] = SPACE.length;
|
201 | INDENTATION_CHARACTERS[TAB] = TAB_SIZE;
|
202 |
|
203 | /**
|
204 | * Gets indentation information for a line.
|
205 | *
|
206 | * @example
|
207 | * getIndent(' foo');
|
208 | * // {indent: 2, stops: {1: 0, 2: 1}}
|
209 | *
|
210 | * getIndent('\tfoo');
|
211 | * // {indent: 4, stops: {4: 0}}
|
212 | *
|
213 | * getIndent(' \tfoo');
|
214 | * // {indent: 4, stops: {1: 0, 2: 1, 4: 2}}
|
215 | *
|
216 | * getIndent('\t foo')
|
217 | * // {indent: 6, stops: {4: 0, 5: 1, 6: 2}}
|
218 | *
|
219 | * @param {string} value - Indented line.
|
220 | * @return {Object}
|
221 | */
|
222 | function getIndent(value) {
|
223 | var index = 0;
|
224 | var indent = 0;
|
225 | var character = value.charAt(index);
|
226 | var stops = {};
|
227 | var size;
|
228 |
|
229 | while (character in INDENTATION_CHARACTERS) {
|
230 | size = INDENTATION_CHARACTERS[character];
|
231 |
|
232 | indent += size;
|
233 |
|
234 | if (size > 1) {
|
235 | indent = Math.floor(indent / size) * size;
|
236 | }
|
237 |
|
238 | stops[indent] = index;
|
239 |
|
240 | character = value.charAt(++index);
|
241 | }
|
242 |
|
243 | return {
|
244 | 'indent': indent,
|
245 | 'stops': stops
|
246 | };
|
247 | }
|
248 |
|
249 | /**
|
250 | * Remove the minimum indent from every line in `value`.
|
251 | * Supports both tab, spaced, and mixed indentation (as
|
252 | * well as possible).
|
253 | *
|
254 | * @example
|
255 | * removeIndentation(' foo'); // 'foo'
|
256 | * removeIndentation(' foo', 2); // ' foo'
|
257 | * removeIndentation('\tfoo', 2); // ' foo'
|
258 | * removeIndentation(' foo\n bar'); // ' foo\n bar'
|
259 | *
|
260 | * @param {string} value
|
261 | * @param {number?} [maximum] - Maximum indentation
|
262 | * to remove.
|
263 | * @return {string} - Unindented `value`.
|
264 | */
|
265 | function removeIndentation(value, maximum) {
|
266 | var values = value.split(NEW_LINE);
|
267 | var position = values.length + 1;
|
268 | var minIndent = Infinity;
|
269 | var matrix = [];
|
270 | var index;
|
271 | var indentation;
|
272 | var stops;
|
273 | var padding;
|
274 |
|
275 | values.unshift(repeat(SPACE, maximum) + EXCLAMATION_MARK);
|
276 |
|
277 | while (position--) {
|
278 | indentation = getIndent(values[position]);
|
279 |
|
280 | matrix[position] = indentation.stops;
|
281 |
|
282 | if (trim(values[position]).length === 0) {
|
283 | continue;
|
284 | }
|
285 |
|
286 | if (indentation.indent) {
|
287 | if (indentation.indent > 0 && indentation.indent < minIndent) {
|
288 | minIndent = indentation.indent;
|
289 | }
|
290 | } else {
|
291 | minIndent = Infinity;
|
292 |
|
293 | break;
|
294 | }
|
295 | }
|
296 |
|
297 | if (minIndent !== Infinity) {
|
298 | position = values.length;
|
299 |
|
300 | while (position--) {
|
301 | stops = matrix[position];
|
302 | index = minIndent;
|
303 |
|
304 | while (index && !(index in stops)) {
|
305 | index--;
|
306 | }
|
307 |
|
308 | if (
|
309 | trim(values[position]).length !== 0 &&
|
310 | minIndent &&
|
311 | index !== minIndent
|
312 | ) {
|
313 | padding = TAB;
|
314 | } else {
|
315 | padding = EMPTY;
|
316 | }
|
317 |
|
318 | values[position] = padding + values[position].slice(
|
319 | index in stops ? stops[index] + 1 : 0
|
320 | );
|
321 | }
|
322 | }
|
323 |
|
324 | values.shift();
|
325 |
|
326 | return values.join(NEW_LINE);
|
327 | }
|
328 |
|
329 | /**
|
330 | * Ensure that `value` is at least indented with
|
331 | * `indent` spaces. Does not support tabs. Does support
|
332 | * multiple lines.
|
333 | *
|
334 | * @example
|
335 | * ensureIndentation('foo', 2); // ' foo'
|
336 | * ensureIndentation(' foo', 4); // ' foo'
|
337 | *
|
338 | * @param {string} value
|
339 | * @param {number} indent - The maximum amount of
|
340 | * spacing to insert.
|
341 | * @return {string} - indented `value`.
|
342 | */
|
343 | function ensureIndentation(value, indent) {
|
344 | var values = value.split(NEW_LINE);
|
345 | var length = values.length;
|
346 | var index = -1;
|
347 | var line;
|
348 | var position;
|
349 |
|
350 | while (++index < length) {
|
351 | line = values[index];
|
352 |
|
353 | position = -1;
|
354 |
|
355 | while (++position < indent) {
|
356 | if (line.charAt(position) !== SPACE) {
|
357 | values[index] = repeat(SPACE, indent - position) + line;
|
358 | break;
|
359 | }
|
360 | }
|
361 | }
|
362 |
|
363 | return values.join(NEW_LINE);
|
364 | }
|
365 |
|
366 | /**
|
367 | * Get the alignment from a table rule.
|
368 | *
|
369 | * @example
|
370 | * getAlignment([':-', ':-:', '-:', '--']);
|
371 | * // ['left', 'center', 'right', null];
|
372 | *
|
373 | * @param {Array.<string>} cells
|
374 | * @return {Array.<string?>}
|
375 | */
|
376 | function getAlignment(cells) {
|
377 | var results = [];
|
378 | var index = -1;
|
379 | var length = cells.length;
|
380 | var alignment;
|
381 |
|
382 | while (++index < length) {
|
383 | alignment = cells[index];
|
384 |
|
385 | if (EXPRESSION_RIGHT_ALIGNMENT.test(alignment)) {
|
386 | results[index] = 'right';
|
387 | } else if (EXPRESSION_CENTER_ALIGNMENT.test(alignment)) {
|
388 | results[index] = 'center';
|
389 | } else if (EXPRESSION_LEFT_ALIGNMENT.test(alignment)) {
|
390 | results[index] = 'left';
|
391 | } else {
|
392 | results[index] = null;
|
393 | }
|
394 | }
|
395 |
|
396 | return results;
|
397 | }
|
398 |
|
399 | /**
|
400 | * Construct a state `toggler`: a function which inverses
|
401 | * `property` in context based on its current value.
|
402 | * The by `toggler` returned function restores that value.
|
403 | *
|
404 | * @example
|
405 | * var context = {};
|
406 | * var key = 'foo';
|
407 | * var val = true;
|
408 | * context[key] = val;
|
409 | * context.enter = stateToggler(key, val);
|
410 | * context[key]; // true
|
411 | * var exit = context.enter();
|
412 | * context[key]; // false
|
413 | * var nested = context.enter();
|
414 | * context[key]; // false
|
415 | * nested();
|
416 | * context[key]; // false
|
417 | * exit();
|
418 | * context[key]; // true
|
419 | *
|
420 | * @param {string} key - Property to toggle.
|
421 | * @param {boolean} state - It's default state.
|
422 | * @return {function(): function()} - Enter.
|
423 | */
|
424 | function stateToggler(key, state) {
|
425 | /**
|
426 | * Construct a toggler for the bound `key`.
|
427 | *
|
428 | * @return {Function} - Exit state.
|
429 | */
|
430 | function enter() {
|
431 | var self = this;
|
432 | var current = self[key];
|
433 |
|
434 | self[key] = !state;
|
435 |
|
436 | /**
|
437 | * State canceler, cancels the state, if allowed.
|
438 | */
|
439 | function exit() {
|
440 | self[key] = current;
|
441 | }
|
442 |
|
443 | return exit;
|
444 | }
|
445 |
|
446 | return enter;
|
447 | }
|
448 |
|
449 | /**
|
450 | * Construct a state toggler which doesn't toggle.
|
451 | *
|
452 | * @example
|
453 | * var context = {};
|
454 | * var key = 'foo';
|
455 | * var val = true;
|
456 | * context[key] = val;
|
457 | * context.enter = noopToggler();
|
458 | * context[key]; // true
|
459 | * var exit = context.enter();
|
460 | * context[key]; // true
|
461 | * exit();
|
462 | * context[key]; // true
|
463 | *
|
464 | * @return {function(): function()} - Enter.
|
465 | */
|
466 | function noopToggler() {
|
467 | /**
|
468 | * No-operation.
|
469 | */
|
470 | function exit() {}
|
471 |
|
472 | /**
|
473 | * @return {Function}
|
474 | */
|
475 | function enter() {
|
476 | return exit;
|
477 | }
|
478 |
|
479 | return enter;
|
480 | }
|
481 |
|
482 | /*
|
483 | * Define nodes of a type which can be merged.
|
484 | */
|
485 |
|
486 | var MERGEABLE_NODES = {};
|
487 |
|
488 | /**
|
489 | * Merge two text nodes: `node` into `prev`.
|
490 | *
|
491 | * @param {Object} prev - Preceding sibling.
|
492 | * @param {Object} node - Following sibling.
|
493 | * @return {Object} - `prev`.
|
494 | */
|
495 | MERGEABLE_NODES.text = function (prev, node) {
|
496 | prev.value += node.value;
|
497 |
|
498 | return prev;
|
499 | };
|
500 |
|
501 | /**
|
502 | * Merge two blockquotes: `node` into `prev`, unless in
|
503 | * CommonMark mode.
|
504 | *
|
505 | * @param {Object} prev - Preceding sibling.
|
506 | * @param {Object} node - Following sibling.
|
507 | * @return {Object} - `prev`, or `node` in CommonMark mode.
|
508 | */
|
509 | MERGEABLE_NODES.blockquote = function (prev, node) {
|
510 | if (this.options.commonmark) {
|
511 | return node;
|
512 | }
|
513 |
|
514 | prev.children = prev.children.concat(node.children);
|
515 |
|
516 | return prev;
|
517 | };
|
518 |
|
519 | /**
|
520 | * Merge two lists: `node` into `prev`. Knows, about
|
521 | * which bullets were used.
|
522 | *
|
523 | * @param {Object} prev - Preceding sibling.
|
524 | * @param {Object} node - Following sibling.
|
525 | * @return {Object} - `prev`, or `node` when the lists are
|
526 | * of different types (a different bullet is used).
|
527 | */
|
528 | MERGEABLE_NODES.list = function (prev, node) {
|
529 | if (
|
530 | !this.currentBullet ||
|
531 | this.currentBullet !== this.previousBullet ||
|
532 | this.currentBullet.length !== 1
|
533 | ) {
|
534 | return node;
|
535 | }
|
536 |
|
537 | prev.children = prev.children.concat(node.children);
|
538 |
|
539 | return prev;
|
540 | };
|
541 |
|
542 | /**
|
543 | * Tokenise a line. Unsets `currentBullet` and
|
544 | * `previousBullet` if more than one lines are found, thus
|
545 | * preventing lists from merging when they use different
|
546 | * bullets.
|
547 | *
|
548 | * @example
|
549 | * tokenizeNewline(eat, '\n\n');
|
550 | *
|
551 | * @param {function(string)} eat
|
552 | * @param {string} $0 - Lines.
|
553 | */
|
554 | function tokenizeNewline(eat, $0) {
|
555 | if ($0.length > 1) {
|
556 | this.currentBullet = null;
|
557 | this.previousBullet = null;
|
558 | }
|
559 |
|
560 | eat($0);
|
561 | }
|
562 |
|
563 | /**
|
564 | * Tokenise an indented code block.
|
565 | *
|
566 | * @example
|
567 | * tokenizeCode(eat, '\tfoo');
|
568 | *
|
569 | * @param {function(string)} eat
|
570 | * @param {string} $0 - Whole code.
|
571 | * @return {Node} - `code` node.
|
572 | */
|
573 | function tokenizeCode(eat, $0) {
|
574 | $0 = trimTrailingLines($0);
|
575 |
|
576 | return eat($0)(this.renderCodeBlock(
|
577 | removeIndentation($0, TAB_SIZE), null, eat)
|
578 | );
|
579 | }
|
580 |
|
581 | /**
|
582 | * Tokenise a fenced code block.
|
583 | *
|
584 | * @example
|
585 | * var $0 = '```js\nfoo()\n```';
|
586 | * tokenizeFences(eat, $0, '', '```', '`', 'js', 'foo()\n');
|
587 | *
|
588 | * @param {function(string)} eat
|
589 | * @param {string} $0 - Whole code.
|
590 | * @param {string} $1 - Initial spacing.
|
591 | * @param {string} $2 - Initial fence.
|
592 | * @param {string} $3 - Fence marker.
|
593 | * @param {string} $4 - Programming language flag.
|
594 | * @param {string} $5 - Content.
|
595 | * @return {Node} - `code` node.
|
596 | */
|
597 | function tokenizeFences(eat, $0, $1, $2, $3, $4, $5) {
|
598 | $0 = trimTrailingLines($0);
|
599 |
|
600 | /*
|
601 | * If the initial fence was preceded by spaces,
|
602 | * exdent that amount of white space from the code
|
603 | * block. Because it's possible that the code block
|
604 | * is exdented, we first have to ensure at least
|
605 | * those spaces are available.
|
606 | */
|
607 |
|
608 | if ($1) {
|
609 | $5 = removeIndentation(ensureIndentation($5, $1.length), $1.length);
|
610 | }
|
611 |
|
612 | return eat($0)(this.renderCodeBlock($5, $4, eat));
|
613 | }
|
614 |
|
615 | /**
|
616 | * Tokenise an ATX-style heading.
|
617 | *
|
618 | * @example
|
619 | * tokenizeHeading(eat, ' # foo', ' ', '#', ' ', 'foo');
|
620 | *
|
621 | * @param {function(string)} eat
|
622 | * @param {string} $0 - Whole heading.
|
623 | * @param {string} $1 - Initial spacing.
|
624 | * @param {string} $2 - Hashes.
|
625 | * @param {string} $3 - Internal spacing.
|
626 | * @param {string} $4 - Content.
|
627 | * @return {Node} - `heading` node.
|
628 | */
|
629 | function tokenizeHeading(eat, $0, $1, $2, $3, $4) {
|
630 | var now = eat.now();
|
631 |
|
632 | now.column += ($1 + $2 + ($3 || '')).length;
|
633 |
|
634 | return eat($0)(this.renderHeading($4, $2.length, now));
|
635 | }
|
636 |
|
637 | /**
|
638 | * Tokenise a Setext-style heading.
|
639 | *
|
640 | * @example
|
641 | * tokenizeLineHeading(eat, 'foo\n===', '', 'foo', '=');
|
642 | *
|
643 | * @param {function(string)} eat
|
644 | * @param {string} $0 - Whole heading.
|
645 | * @param {string} $1 - Initial spacing.
|
646 | * @param {string} $2 - Content.
|
647 | * @param {string} $3 - Underline marker.
|
648 | * @return {Node} - `heading` node.
|
649 | */
|
650 | function tokenizeLineHeading(eat, $0, $1, $2, $3) {
|
651 | var now = eat.now();
|
652 |
|
653 | now.column += $1.length;
|
654 |
|
655 | return eat($0)(this.renderHeading($2, $3 === EQUALS ? 1 : 2, now));
|
656 | }
|
657 |
|
658 | /**
|
659 | * Tokenise a horizontal rule.
|
660 | *
|
661 | * @example
|
662 | * tokenizeHorizontalRule(eat, '***');
|
663 | *
|
664 | * @param {function(string)} eat
|
665 | * @param {string} $0 - Whole rule.
|
666 | * @return {Node} - `horizontalRule` node.
|
667 | */
|
668 | function tokenizeHorizontalRule(eat, $0) {
|
669 | return eat($0)(this.renderVoid(HORIZONTAL_RULE));
|
670 | }
|
671 |
|
672 | /**
|
673 | * Tokenise a blockquote.
|
674 | *
|
675 | * @example
|
676 | * tokenizeBlockquote(eat, '> Foo');
|
677 | *
|
678 | * @param {function(string)} eat
|
679 | * @param {string} $0 - Whole blockquote.
|
680 | * @return {Node} - `blockquote` node.
|
681 | */
|
682 | function tokenizeBlockquote(eat, $0) {
|
683 | var now = eat.now();
|
684 | var indent = this.indent(now.line);
|
685 | var value = trimTrailingLines($0);
|
686 | var add = eat(value);
|
687 |
|
688 | value = value.replace(EXPRESSION_BLOCK_QUOTE, function (prefix) {
|
689 | indent(prefix.length);
|
690 |
|
691 | return '';
|
692 | });
|
693 |
|
694 | return add(this.renderBlockquote(value, now));
|
695 | }
|
696 |
|
697 | /**
|
698 | * Tokenise a list.
|
699 | *
|
700 | * @example
|
701 | * tokenizeList(eat, '- Foo', '', '-');
|
702 | *
|
703 | * @param {function(string)} eat
|
704 | * @param {string} $0 - Whole list.
|
705 | * @param {string} $1 - Indent.
|
706 | * @param {string} $2 - Bullet.
|
707 | * @return {Node} - `list` node.
|
708 | */
|
709 | function tokenizeList(eat, $0, $1, $2) {
|
710 | var self = this;
|
711 | var firstBullet = $2;
|
712 | var value = trimTrailingLines($0);
|
713 | var matches = value.match(self.rules.item);
|
714 | var length = matches.length;
|
715 | var index = 0;
|
716 | var isLoose = false;
|
717 | var now;
|
718 | var bullet;
|
719 | var item;
|
720 | var enterTop;
|
721 | var exitBlockquote;
|
722 | var node;
|
723 | var indent;
|
724 | var size;
|
725 | var position;
|
726 | var end;
|
727 |
|
728 | /*
|
729 | * Determine if all list-items belong to the
|
730 | * same list.
|
731 | */
|
732 |
|
733 | if (!self.options.pedantic) {
|
734 | while (++index < length) {
|
735 | bullet = self.rules.bullet.exec(matches[index])[0];
|
736 |
|
737 | if (
|
738 | firstBullet !== bullet &&
|
739 | (
|
740 | firstBullet.length === 1 && bullet.length === 1 ||
|
741 | bullet.charAt(bullet.length - 1) !==
|
742 | firstBullet.charAt(firstBullet.length - 1)
|
743 | )
|
744 | ) {
|
745 | matches = matches.slice(0, index);
|
746 | matches[index - 1] = trimTrailingLines(matches[index - 1]);
|
747 |
|
748 | length = matches.length;
|
749 |
|
750 | break;
|
751 | }
|
752 | }
|
753 | }
|
754 |
|
755 | if (self.options.commonmark) {
|
756 | index = -1;
|
757 |
|
758 | while (++index < length) {
|
759 | item = matches[index];
|
760 | indent = self.rules.indent.exec(item);
|
761 | indent = indent[1] + repeat(SPACE, indent[2].length) + indent[3];
|
762 | size = getIndent(indent).indent;
|
763 | position = indent.length;
|
764 | end = item.length;
|
765 |
|
766 | while (++position < end) {
|
767 | if (
|
768 | item.charAt(position) === NEW_LINE &&
|
769 | item.charAt(position - 1) === NEW_LINE &&
|
770 | getIndent(item.slice(position + 1)).indent < size
|
771 | ) {
|
772 | matches[index] = item.slice(0, position - 1);
|
773 |
|
774 | matches = matches.slice(0, index + 1);
|
775 | length = matches.length;
|
776 |
|
777 | break;
|
778 | }
|
779 | }
|
780 | }
|
781 | }
|
782 |
|
783 | self.previousBullet = self.currentBullet;
|
784 | self.currentBullet = firstBullet;
|
785 |
|
786 | index = -1;
|
787 |
|
788 | node = eat(matches.join(NEW_LINE)).reset(
|
789 | self.renderList([], firstBullet)
|
790 | );
|
791 |
|
792 | enterTop = self.exitTop();
|
793 | exitBlockquote = self.enterBlockquote();
|
794 |
|
795 | while (++index < length) {
|
796 | item = matches[index];
|
797 | now = eat.now();
|
798 |
|
799 | item = eat(item)(self.renderListItem(item, now), node);
|
800 |
|
801 | if (item.loose) {
|
802 | isLoose = true;
|
803 | }
|
804 |
|
805 | if (index !== length - 1) {
|
806 | eat(NEW_LINE);
|
807 | }
|
808 | }
|
809 |
|
810 | node.loose = isLoose;
|
811 |
|
812 | enterTop();
|
813 | exitBlockquote();
|
814 |
|
815 | return node;
|
816 | }
|
817 |
|
818 | /**
|
819 | * Tokenise HTML.
|
820 | *
|
821 | * @example
|
822 | * tokenizeHtml(eat, '<span>foo</span>');
|
823 | *
|
824 | * @param {function(string)} eat
|
825 | * @param {string} $0 - Whole HTML.
|
826 | * @return {Node} - `html` node.
|
827 | */
|
828 | function tokenizeHtml(eat, $0) {
|
829 | $0 = trimTrailingLines($0);
|
830 |
|
831 | return eat($0)(this.renderRaw(HTML, $0));
|
832 | }
|
833 |
|
834 | /**
|
835 | * Tokenise a definition.
|
836 | *
|
837 | * @example
|
838 | * var $0 = '[foo]: http://example.com "Example Domain"';
|
839 | * var $1 = 'foo';
|
840 | * var $2 = 'http://example.com';
|
841 | * var $3 = 'Example Domain';
|
842 | * tokenizeDefinition(eat, $0, $1, $2, $3);
|
843 | *
|
844 | * @property {boolean} onlyAtTop
|
845 | * @property {boolean} notInBlockquote
|
846 | * @param {function(string)} eat
|
847 | * @param {string} $0 - Whole definition.
|
848 | * @param {string} $1 - Key.
|
849 | * @param {string} $2 - URL.
|
850 | * @param {string} $3 - Title.
|
851 | * @return {Node} - `definition` node.
|
852 | */
|
853 | function tokenizeDefinition(eat, $0, $1, $2, $3) {
|
854 | var link = $2;
|
855 |
|
856 | /*
|
857 | * Remove angle-brackets from `link`.
|
858 | */
|
859 |
|
860 | if (link.charAt(0) === LT && link.charAt(link.length - 1) === GT) {
|
861 | link = link.slice(1, -1);
|
862 | }
|
863 |
|
864 | return eat($0)({
|
865 | 'type': 'definition',
|
866 | 'identifier': normalize($1),
|
867 | 'title': $3 ? decode(this.descape($3), eat) : null,
|
868 | 'link': decode(this.descape(link), eat)
|
869 | });
|
870 | }
|
871 |
|
872 | tokenizeDefinition.onlyAtTop = true;
|
873 | tokenizeDefinition.notInBlockquote = true;
|
874 |
|
875 | /**
|
876 | * Tokenise YAML front matter.
|
877 | *
|
878 | * @example
|
879 | * var $0 = '---\nfoo: bar\n---';
|
880 | * var $1 = 'foo: bar';
|
881 | * tokenizeYAMLFrontMatter(eat, $0, $1);
|
882 | *
|
883 | * @property {boolean} onlyAtStart
|
884 | * @param {function(string)} eat
|
885 | * @param {string} $0 - Whole front matter.
|
886 | * @param {string} $1 - Content.
|
887 | * @return {Node} - `yaml` node.
|
888 | */
|
889 | function tokenizeYAMLFrontMatter(eat, $0, $1) {
|
890 | return eat($0)(this.renderRaw(YAML, $1 ? trimTrailingLines($1) : EMPTY));
|
891 | }
|
892 |
|
893 | tokenizeYAMLFrontMatter.onlyAtStart = true;
|
894 |
|
895 | /**
|
896 | * Tokenise a footnote definition.
|
897 | *
|
898 | * @example
|
899 | * var $0 = '[foo]: Bar.';
|
900 | * var $1 = '[foo]';
|
901 | * var $2 = 'foo';
|
902 | * var $3 = 'Bar.';
|
903 | * tokenizeFootnoteDefinition(eat, $0, $1, $2, $3);
|
904 | *
|
905 | * @property {boolean} onlyAtTop
|
906 | * @property {boolean} notInBlockquote
|
907 | * @param {function(string)} eat
|
908 | * @param {string} $0 - Whole definition.
|
909 | * @param {string} $1 - Whole key.
|
910 | * @param {string} $2 - Key.
|
911 | * @param {string} $3 - Whole value.
|
912 | * @return {Node} - `footnoteDefinition` node.
|
913 | */
|
914 | function tokenizeFootnoteDefinition(eat, $0, $1, $2, $3) {
|
915 | var self = this;
|
916 | var now = eat.now();
|
917 | var indent = self.indent(now.line);
|
918 |
|
919 | $3 = $3.replace(EXPRESSION_INITIAL_TAB, function (value) {
|
920 | indent(value.length);
|
921 |
|
922 | return EMPTY;
|
923 | });
|
924 |
|
925 | now.column += $1.length;
|
926 |
|
927 | return eat($0)(self.renderFootnoteDefinition(normalize($2), $3, now));
|
928 | }
|
929 |
|
930 | tokenizeFootnoteDefinition.onlyAtTop = true;
|
931 | tokenizeFootnoteDefinition.notInBlockquote = true;
|
932 |
|
933 | /**
|
934 | * Tokenise a table.
|
935 | *
|
936 | * @example
|
937 | * var $0 = ' | foo |\n | --- |\n | bar |';
|
938 | * var $1 = ' | foo |';
|
939 | * var $2 = '| foo |';
|
940 | * var $3 = ' | --- |';
|
941 | * var $4 = '| --- |';
|
942 | * var $5 = ' | bar |';
|
943 | * tokenizeTable(eat, $0, $1, $2, $3, $4, $5);
|
944 | *
|
945 | * @property {boolean} onlyAtTop
|
946 | * @param {function(string)} eat
|
947 | * @param {string} $0 - Whole table.
|
948 | * @param {string} $1 - Whole heading.
|
949 | * @param {string} $2 - Trimmed heading.
|
950 | * @param {string} $3 - Whole alignment.
|
951 | * @param {string} $4 - Trimmed alignment.
|
952 | * @param {string} $5 - Rows.
|
953 | * @return {Node} - `table` node.
|
954 | */
|
955 | function tokenizeTable(eat, $0, $1, $2, $3, $4, $5) {
|
956 | var self = this;
|
957 | var length;
|
958 | var index;
|
959 | var node;
|
960 |
|
961 | $0 = trimTrailingLines($0);
|
962 |
|
963 | node = eat($0).reset({
|
964 | 'type': TABLE,
|
965 | 'align': [],
|
966 | 'children': []
|
967 | });
|
968 |
|
969 | /**
|
970 | * Eat a row of type `type`.
|
971 | *
|
972 | * @param {string} type - Type of the returned node,
|
973 | * such as `tableHeader` or `tableRow`.
|
974 | * @param {string} value - Row, including initial and
|
975 | * final fences.
|
976 | */
|
977 | function renderRow(type, value) {
|
978 | var row = eat(value).reset(self.renderParent(type, []), node);
|
979 | var length = value.length + 1;
|
980 | var index = -1;
|
981 | var queue = '';
|
982 | var cell = '';
|
983 | var preamble = true;
|
984 | var count;
|
985 | var opening;
|
986 | var character;
|
987 | var subvalue;
|
988 | var now;
|
989 |
|
990 | while (++index < length) {
|
991 | character = value.charAt(index);
|
992 |
|
993 | if (character === '\t' || character === ' ') {
|
994 | if (cell) {
|
995 | queue += character;
|
996 | } else {
|
997 | eat(character);
|
998 | }
|
999 |
|
1000 | continue;
|
1001 | }
|
1002 |
|
1003 | if (character === '|' || character === '') {
|
1004 | if (preamble) {
|
1005 | eat(character);
|
1006 | } else {
|
1007 | if (character && opening) {
|
1008 | // cell += queue + character;
|
1009 | queue += character;
|
1010 | continue;
|
1011 | }
|
1012 |
|
1013 | if ((cell || character) && !preamble) {
|
1014 | subvalue = cell;
|
1015 |
|
1016 | if (queue.length > 1) {
|
1017 | if (character) {
|
1018 | subvalue += queue.slice(0, queue.length - 1);
|
1019 | queue = queue.charAt(queue.length - 1);
|
1020 | } else {
|
1021 | subvalue += queue;
|
1022 | queue = '';
|
1023 | }
|
1024 | }
|
1025 |
|
1026 | now = eat.now();
|
1027 |
|
1028 | eat(subvalue)(
|
1029 | self.renderInline(TABLE_CELL, cell, now), row
|
1030 | );
|
1031 | }
|
1032 |
|
1033 | eat(queue + character);
|
1034 |
|
1035 | queue = '';
|
1036 | cell = '';
|
1037 | }
|
1038 | } else {
|
1039 | if (queue) {
|
1040 | cell += queue;
|
1041 | queue = '';
|
1042 | }
|
1043 |
|
1044 | cell += character;
|
1045 |
|
1046 | if (character === '\\' && index !== length - 2) {
|
1047 | cell += value.charAt(index + 1);
|
1048 | index++;
|
1049 | }
|
1050 |
|
1051 | if (character === '`') {
|
1052 | count = 1;
|
1053 |
|
1054 | while (value.charAt(index + 1) === character) {
|
1055 | cell += character;
|
1056 | index++;
|
1057 | count++;
|
1058 | }
|
1059 |
|
1060 | if (!opening) {
|
1061 | opening = count;
|
1062 | } else if (count >= opening) {
|
1063 | opening = 0;
|
1064 | }
|
1065 | }
|
1066 | }
|
1067 |
|
1068 | preamble = false;
|
1069 | }
|
1070 | }
|
1071 |
|
1072 | /*
|
1073 | * Add the table's header.
|
1074 | */
|
1075 |
|
1076 | renderRow(TABLE_HEADER, $1);
|
1077 |
|
1078 | eat(NEW_LINE);
|
1079 |
|
1080 | /*
|
1081 | * Add the table's alignment.
|
1082 | */
|
1083 |
|
1084 | eat($3);
|
1085 |
|
1086 | $4 = $4
|
1087 | .replace(EXPRESSION_TABLE_FENCE, EMPTY)
|
1088 | .split(EXPRESSION_TABLE_BORDER);
|
1089 |
|
1090 | node.align = getAlignment($4);
|
1091 |
|
1092 | /*
|
1093 | * Add the table rows to table's children.
|
1094 | */
|
1095 |
|
1096 | $5 = trimTrailingLines($5).split(NEW_LINE);
|
1097 |
|
1098 | index = -1;
|
1099 | length = $5.length;
|
1100 |
|
1101 | while (++index < length) {
|
1102 | renderRow(TABLE_ROW, $5[index]);
|
1103 |
|
1104 | if (index !== length - 1) {
|
1105 | eat(NEW_LINE);
|
1106 | }
|
1107 | }
|
1108 |
|
1109 | return node;
|
1110 | }
|
1111 |
|
1112 | tokenizeTable.onlyAtTop = true;
|
1113 |
|
1114 | /**
|
1115 | * Tokenise a paragraph node.
|
1116 | *
|
1117 | * @example
|
1118 | * tokenizeParagraph(eat, 'Foo.');
|
1119 | *
|
1120 | * @param {function(string)} eat
|
1121 | * @param {string} $0 - Whole paragraph.
|
1122 | * @return {Node?} - `paragraph` node, when the node does
|
1123 | * not just contain white space.
|
1124 | */
|
1125 | function tokenizeParagraph(eat, $0) {
|
1126 | var now = eat.now();
|
1127 |
|
1128 | if (trim($0) === EMPTY) {
|
1129 | eat($0);
|
1130 |
|
1131 | return null;
|
1132 | }
|
1133 |
|
1134 | $0 = trimTrailingLines($0);
|
1135 |
|
1136 | return eat($0)(this.renderInline(PARAGRAPH, $0, now));
|
1137 | }
|
1138 |
|
1139 | /**
|
1140 | * Tokenise a text node.
|
1141 | *
|
1142 | * @example
|
1143 | * tokenizeText(eat, 'foo');
|
1144 | *
|
1145 | * @param {function(string)} eat
|
1146 | * @param {string} $0 - Whole text.
|
1147 | * @return {Node} - `text` node.
|
1148 | */
|
1149 | function tokenizeText(eat, $0) {
|
1150 | return eat($0)(this.renderRaw(TEXT, $0));
|
1151 | }
|
1152 |
|
1153 | /**
|
1154 | * Create a code-block node.
|
1155 | *
|
1156 | * @example
|
1157 | * renderCodeBlock('foo()', 'js', now());
|
1158 | *
|
1159 | * @param {string?} [value] - Code.
|
1160 | * @param {string?} [language] - Optional language flag.
|
1161 | * @param {Function} eat
|
1162 | * @return {Object} - `code` node.
|
1163 | */
|
1164 | function renderCodeBlock(value, language, eat) {
|
1165 | return {
|
1166 | 'type': CODE,
|
1167 | 'lang': language ? decode(this.descape(language), eat) : null,
|
1168 | 'value': trimTrailingLines(value || EMPTY)
|
1169 | };
|
1170 | }
|
1171 |
|
1172 | /**
|
1173 | * Create a list node.
|
1174 | *
|
1175 | * @example
|
1176 | * var children = [renderListItem('- foo')];
|
1177 | * renderList(children, '-');
|
1178 | *
|
1179 | * @param {string} children - Children.
|
1180 | * @param {string} bullet - First bullet.
|
1181 | * @return {Object} - `list` node.
|
1182 | */
|
1183 | function renderList(children, bullet) {
|
1184 | var start = parseInt(bullet, 10);
|
1185 |
|
1186 | if (start !== start) {
|
1187 | start = null;
|
1188 | }
|
1189 |
|
1190 | /*
|
1191 | * `loose` should be added later.
|
1192 | */
|
1193 |
|
1194 | return {
|
1195 | 'type': LIST,
|
1196 | 'ordered': bullet.length > 1,
|
1197 | 'start': start,
|
1198 | 'loose': null,
|
1199 | 'children': children
|
1200 | };
|
1201 | }
|
1202 |
|
1203 | /**
|
1204 | * Create a list-item using overly simple mechanics.
|
1205 | *
|
1206 | * @example
|
1207 | * renderPedanticListItem('- _foo_', now());
|
1208 | *
|
1209 | * @param {string} value - List-item.
|
1210 | * @param {Object} position - List-item location.
|
1211 | * @return {string} - Cleaned `value`.
|
1212 | */
|
1213 | function renderPedanticListItem(value, position) {
|
1214 | var self = this;
|
1215 | var indent = self.indent(position.line);
|
1216 |
|
1217 | /**
|
1218 | * A simple replacer which removed all matches,
|
1219 | * and adds their length to `offset`.
|
1220 | *
|
1221 | * @param {string} $0
|
1222 | * @return {string}
|
1223 | */
|
1224 | function replacer($0) {
|
1225 | indent($0.length);
|
1226 |
|
1227 | return EMPTY;
|
1228 | }
|
1229 |
|
1230 | /*
|
1231 | * Remove the list-item's bullet.
|
1232 | */
|
1233 |
|
1234 | value = value.replace(EXPRESSION_PEDANTIC_BULLET, replacer);
|
1235 |
|
1236 | /*
|
1237 | * The initial line was also matched by the below, so
|
1238 | * we reset the `line`.
|
1239 | */
|
1240 |
|
1241 | indent = self.indent(position.line);
|
1242 |
|
1243 | return value.replace(EXPRESSION_INITIAL_INDENT, replacer);
|
1244 | }
|
1245 |
|
1246 | /**
|
1247 | * Create a list-item using sane mechanics.
|
1248 | *
|
1249 | * @example
|
1250 | * renderNormalListItem('- _foo_', now());
|
1251 | *
|
1252 | * @param {string} value - List-item.
|
1253 | * @param {Object} position - List-item location.
|
1254 | * @return {string} - Cleaned `value`.
|
1255 | */
|
1256 | function renderNormalListItem(value, position) {
|
1257 | var self = this;
|
1258 | var indent = self.indent(position.line);
|
1259 | var bullet;
|
1260 | var rest;
|
1261 | var lines;
|
1262 | var trimmedLines;
|
1263 | var index;
|
1264 | var length;
|
1265 | var max;
|
1266 |
|
1267 | /*
|
1268 | * Remove the list-item's bullet.
|
1269 | */
|
1270 |
|
1271 | value = value.replace(EXPRESSION_BULLET, function ($0, $1, $2, $3, $4) {
|
1272 | bullet = $1 + $2 + $3;
|
1273 | rest = $4;
|
1274 |
|
1275 | /*
|
1276 | * Make sure that the first nine numbered list items
|
1277 | * can indent with an extra space. That is, when
|
1278 | * the bullet did not receive an extra final space.
|
1279 | */
|
1280 |
|
1281 | if (Number($2) < 10 && bullet.length % 2 === 1) {
|
1282 | $2 = SPACE + $2;
|
1283 | }
|
1284 |
|
1285 | max = $1 + repeat(SPACE, $2.length) + $3;
|
1286 |
|
1287 | return max + rest;
|
1288 | });
|
1289 |
|
1290 | lines = value.split(NEW_LINE);
|
1291 |
|
1292 | trimmedLines = removeIndentation(
|
1293 | value, getIndent(max).indent
|
1294 | ).split(NEW_LINE);
|
1295 |
|
1296 | /*
|
1297 | * We replaced the initial bullet with something
|
1298 | * else above, which was used to trick
|
1299 | * `removeIndentation` into removing some more
|
1300 | * characters when possible. However, that could
|
1301 | * result in the initial line to be stripped more
|
1302 | * than it should be.
|
1303 | */
|
1304 |
|
1305 | trimmedLines[0] = rest;
|
1306 |
|
1307 | indent(bullet.length);
|
1308 |
|
1309 | index = 0;
|
1310 | length = lines.length;
|
1311 |
|
1312 | while (++index < length) {
|
1313 | indent(lines[index].length - trimmedLines[index].length);
|
1314 | }
|
1315 |
|
1316 | return trimmedLines.join(NEW_LINE);
|
1317 | }
|
1318 |
|
1319 | /*
|
1320 | * A map of two functions which can create list items.
|
1321 | */
|
1322 |
|
1323 | var LIST_ITEM_MAP = {};
|
1324 |
|
1325 | LIST_ITEM_MAP.true = renderPedanticListItem;
|
1326 | LIST_ITEM_MAP.false = renderNormalListItem;
|
1327 |
|
1328 | /**
|
1329 | * Create a list-item node.
|
1330 | *
|
1331 | * @example
|
1332 | * renderListItem('- _foo_', now());
|
1333 | *
|
1334 | * @param {Object} value - List-item.
|
1335 | * @param {Object} position - List-item location.
|
1336 | * @return {Object} - `listItem` node.
|
1337 | */
|
1338 | function renderListItem(value, position) {
|
1339 | var self = this;
|
1340 | var checked = null;
|
1341 | var node;
|
1342 | var task;
|
1343 | var indent;
|
1344 |
|
1345 | value = LIST_ITEM_MAP[self.options.pedantic].apply(self, arguments);
|
1346 |
|
1347 | if (self.options.gfm) {
|
1348 | task = value.match(EXPRESSION_TASK_ITEM);
|
1349 |
|
1350 | if (task) {
|
1351 | indent = task[0].length;
|
1352 | checked = task[1].toLowerCase() === 'x';
|
1353 |
|
1354 | self.indent(position.line)(indent);
|
1355 | value = value.slice(indent);
|
1356 | }
|
1357 | }
|
1358 |
|
1359 | node = {
|
1360 | 'type': LIST_ITEM,
|
1361 | 'loose': EXPRESSION_LOOSE_LIST_ITEM.test(value) ||
|
1362 | value.charAt(value.length - 1) === NEW_LINE
|
1363 | };
|
1364 |
|
1365 | if (self.options.gfm) {
|
1366 | node.checked = checked;
|
1367 | }
|
1368 |
|
1369 | node.children = self.tokenizeBlock(value, position);
|
1370 |
|
1371 | return node;
|
1372 | }
|
1373 |
|
1374 | /**
|
1375 | * Create a footnote-definition node.
|
1376 | *
|
1377 | * @example
|
1378 | * renderFootnoteDefinition('1', '_foo_', now());
|
1379 | *
|
1380 | * @param {string} identifier - Unique reference.
|
1381 | * @param {string} value - Contents
|
1382 | * @param {Object} position - Definition location.
|
1383 | * @return {Object} - `footnoteDefinition` node.
|
1384 | */
|
1385 | function renderFootnoteDefinition(identifier, value, position) {
|
1386 | var self = this;
|
1387 | var exitBlockquote = self.enterBlockquote();
|
1388 | var node;
|
1389 |
|
1390 | node = {
|
1391 | 'type': FOOTNOTE_DEFINITION,
|
1392 | 'identifier': identifier,
|
1393 | 'children': self.tokenizeBlock(value, position)
|
1394 | };
|
1395 |
|
1396 | exitBlockquote();
|
1397 |
|
1398 | return node;
|
1399 | }
|
1400 |
|
1401 | /**
|
1402 | * Create a heading node.
|
1403 | *
|
1404 | * @example
|
1405 | * renderHeading('_foo_', 1, now());
|
1406 | *
|
1407 | * @param {string} value - Content.
|
1408 | * @param {number} depth - Heading depth.
|
1409 | * @param {Object} position - Heading content location.
|
1410 | * @return {Object} - `heading` node
|
1411 | */
|
1412 | function renderHeading(value, depth, position) {
|
1413 | return {
|
1414 | 'type': HEADING,
|
1415 | 'depth': depth,
|
1416 | 'children': this.tokenizeInline(value, position)
|
1417 | };
|
1418 | }
|
1419 |
|
1420 | /**
|
1421 | * Create a blockquote node.
|
1422 | *
|
1423 | * @example
|
1424 | * renderBlockquote('_foo_', eat);
|
1425 | *
|
1426 | * @param {string} value - Content.
|
1427 | * @param {Object} now - Position.
|
1428 | * @return {Object} - `blockquote` node.
|
1429 | */
|
1430 | function renderBlockquote(value, now) {
|
1431 | var self = this;
|
1432 | var exitBlockquote = self.enterBlockquote();
|
1433 | var node = {
|
1434 | 'type': BLOCKQUOTE,
|
1435 | 'children': this.tokenizeBlock(value, now)
|
1436 | };
|
1437 |
|
1438 | exitBlockquote();
|
1439 |
|
1440 | return node;
|
1441 | }
|
1442 |
|
1443 | /**
|
1444 | * Create a void node.
|
1445 | *
|
1446 | * @example
|
1447 | * renderVoid('horizontalRule');
|
1448 | *
|
1449 | * @param {string} type - Node type.
|
1450 | * @return {Object} - Node of type `type`.
|
1451 | */
|
1452 | function renderVoid(type) {
|
1453 | return {
|
1454 | 'type': type
|
1455 | };
|
1456 | }
|
1457 |
|
1458 | /**
|
1459 | * Create a parent.
|
1460 | *
|
1461 | * @example
|
1462 | * renderParent('paragraph', '_foo_');
|
1463 | *
|
1464 | * @param {string} type - Node type.
|
1465 | * @param {Array.<Object>} children - Child nodes.
|
1466 | * @return {Object} - Node of type `type`.
|
1467 | */
|
1468 | function renderParent(type, children) {
|
1469 | return {
|
1470 | 'type': type,
|
1471 | 'children': children
|
1472 | };
|
1473 | }
|
1474 |
|
1475 | /**
|
1476 | * Create a raw node.
|
1477 | *
|
1478 | * @example
|
1479 | * renderRaw('inlineCode', 'foo()');
|
1480 | *
|
1481 | * @param {string} type - Node type.
|
1482 | * @param {string} value - Contents.
|
1483 | * @return {Object} - Node of type `type`.
|
1484 | */
|
1485 | function renderRaw(type, value) {
|
1486 | return {
|
1487 | 'type': type,
|
1488 | 'value': value
|
1489 | };
|
1490 | }
|
1491 |
|
1492 | /**
|
1493 | * Create a link node.
|
1494 | *
|
1495 | * @example
|
1496 | * renderLink(true, 'example.com', 'example', 'Example Domain', now(), eat);
|
1497 | * renderLink(false, 'fav.ico', 'example', 'Example Domain', now(), eat);
|
1498 | *
|
1499 | * @param {boolean} isLink - Whether linking to a document
|
1500 | * or an image.
|
1501 | * @param {string} href - URI reference.
|
1502 | * @param {string} text - Content.
|
1503 | * @param {string?} title - Title.
|
1504 | * @param {Object} position - Location of link.
|
1505 | * @param {function(string)} eat
|
1506 | * @return {Object} - `link` or `image` node.
|
1507 | */
|
1508 | function renderLink(isLink, href, text, title, position, eat) {
|
1509 | var self = this;
|
1510 | var exitLink = self.enterLink();
|
1511 | var node;
|
1512 |
|
1513 | node = {
|
1514 | 'type': isLink ? LINK : IMAGE,
|
1515 | 'title': title ? decode(self.descape(title), eat) : null
|
1516 | };
|
1517 |
|
1518 | href = decode(href, eat);
|
1519 |
|
1520 | if (isLink) {
|
1521 | node.href = href;
|
1522 | node.children = self.tokenizeInline(text, position);
|
1523 | } else {
|
1524 | node.src = href;
|
1525 | node.alt = text ? decode(self.descape(text), eat) : null;
|
1526 | }
|
1527 |
|
1528 | exitLink();
|
1529 |
|
1530 | return node;
|
1531 | }
|
1532 |
|
1533 | /**
|
1534 | * Create a footnote node.
|
1535 | *
|
1536 | * @example
|
1537 | * renderFootnote('_foo_', now());
|
1538 | *
|
1539 | * @param {string} value - Contents.
|
1540 | * @param {Object} position - Location of footnote.
|
1541 | * @return {Object} - `footnote` node.
|
1542 | */
|
1543 | function renderFootnote(value, position) {
|
1544 | return this.renderInline(FOOTNOTE, value, position);
|
1545 | }
|
1546 |
|
1547 | /**
|
1548 | * Add a node with inline content.
|
1549 | *
|
1550 | * @example
|
1551 | * renderInline('strong', '_foo_', now());
|
1552 | *
|
1553 | * @param {string} type - Node type.
|
1554 | * @param {string} value - Contents.
|
1555 | * @param {Object} position - Location of node.
|
1556 | * @return {Object} - Node of type `type`.
|
1557 | */
|
1558 | function renderInline(type, value, position) {
|
1559 | return this.renderParent(type, this.tokenizeInline(value, position));
|
1560 | }
|
1561 |
|
1562 | /**
|
1563 | * Add a node with block content.
|
1564 | *
|
1565 | * @example
|
1566 | * renderBlock('blockquote', 'Foo.', now());
|
1567 | *
|
1568 | * @param {string} type - Node type.
|
1569 | * @param {string} value - Contents.
|
1570 | * @param {Object} position - Location of node.
|
1571 | * @return {Object} - Node of type `type`.
|
1572 | */
|
1573 | function renderBlock(type, value, position) {
|
1574 | return this.renderParent(type, this.tokenizeBlock(value, position));
|
1575 | }
|
1576 |
|
1577 | /**
|
1578 | * Tokenise an escape sequence.
|
1579 | *
|
1580 | * @example
|
1581 | * tokenizeEscape(eat, '\\a', 'a');
|
1582 | *
|
1583 | * @param {function(string)} eat
|
1584 | * @param {string} $0 - Whole escape.
|
1585 | * @param {string} $1 - Escaped character.
|
1586 | * @return {Node} - `escape` node.
|
1587 | */
|
1588 | function tokenizeEscape(eat, $0, $1) {
|
1589 | return eat($0)(this.renderRaw(ESCAPE, $1));
|
1590 | }
|
1591 |
|
1592 | /**
|
1593 | * Tokenise a URL in carets.
|
1594 | *
|
1595 | * @example
|
1596 | * tokenizeAutoLink(eat, '<http://foo.bar>', 'http://foo.bar', '');
|
1597 | *
|
1598 | * @property {boolean} notInLink
|
1599 | * @param {function(string)} eat
|
1600 | * @param {string} $0 - Whole link.
|
1601 | * @param {string} $1 - URL.
|
1602 | * @param {string?} [$2] - Protocol or at.
|
1603 | * @return {Node} - `link` node.
|
1604 | */
|
1605 | function tokenizeAutoLink(eat, $0, $1, $2) {
|
1606 | var self = this;
|
1607 | var href = $1;
|
1608 | var text = $1;
|
1609 | var now = eat.now();
|
1610 | var offset = 1;
|
1611 | var tokenize;
|
1612 | var node;
|
1613 |
|
1614 | if ($2 === AT_SIGN) {
|
1615 | if (
|
1616 | text.substr(0, MAILTO_PROTOCOL.length).toLowerCase() !==
|
1617 | MAILTO_PROTOCOL
|
1618 | ) {
|
1619 | href = MAILTO_PROTOCOL + text;
|
1620 | } else {
|
1621 | text = text.substr(MAILTO_PROTOCOL.length);
|
1622 | offset += MAILTO_PROTOCOL.length;
|
1623 | }
|
1624 | }
|
1625 |
|
1626 | now.column += offset;
|
1627 |
|
1628 | /*
|
1629 | * Temporarily remove support for escapes in autolinks.
|
1630 | */
|
1631 |
|
1632 | tokenize = self.inlineTokenizers.escape;
|
1633 | self.inlineTokenizers.escape = null;
|
1634 |
|
1635 | node = eat($0)(self.renderLink(true, href, text, null, now, eat));
|
1636 |
|
1637 | self.inlineTokenizers.escape = tokenize;
|
1638 |
|
1639 | return node;
|
1640 | }
|
1641 |
|
1642 | tokenizeAutoLink.notInLink = true;
|
1643 |
|
1644 | /**
|
1645 | * Tokenise a URL in text.
|
1646 | *
|
1647 | * @example
|
1648 | * tokenizeURL(eat, 'http://foo.bar');
|
1649 | *
|
1650 | * @property {boolean} notInLink
|
1651 | * @param {function(string)} eat
|
1652 | * @param {string} $0 - Whole link.
|
1653 | * @return {Node} - `link` node.
|
1654 | */
|
1655 | function tokenizeURL(eat, $0) {
|
1656 | var now = eat.now();
|
1657 |
|
1658 | return eat($0)(this.renderLink(true, $0, $0, null, now, eat));
|
1659 | }
|
1660 |
|
1661 | tokenizeURL.notInLink = true;
|
1662 |
|
1663 | /**
|
1664 | * Tokenise an HTML tag.
|
1665 | *
|
1666 | * @example
|
1667 | * tokenizeTag(eat, '<span foo="bar">');
|
1668 | *
|
1669 | * @param {function(string)} eat
|
1670 | * @param {string} $0 - Content.
|
1671 | * @return {Node} - `html` node.
|
1672 | */
|
1673 | function tokenizeTag(eat, $0) {
|
1674 | var self = this;
|
1675 |
|
1676 | if (!self.inLink && EXPRESSION_HTML_LINK_OPEN.test($0)) {
|
1677 | self.inLink = true;
|
1678 | } else if (self.inLink && EXPRESSION_HTML_LINK_CLOSE.test($0)) {
|
1679 | self.inLink = false;
|
1680 | }
|
1681 |
|
1682 | return eat($0)(self.renderRaw(HTML, $0));
|
1683 | }
|
1684 |
|
1685 | /**
|
1686 | * Tokenise a link.
|
1687 | *
|
1688 | * @example
|
1689 | * tokenizeLink(
|
1690 | * eat, '![foo](fav.ico "Favicon")', '![', 'foo', null,
|
1691 | * 'fav.ico', 'Foo Domain'
|
1692 | * );
|
1693 | *
|
1694 | * @param {function(string)} eat
|
1695 | * @param {string} $0 - Whole link.
|
1696 | * @param {string} $1 - Prefix.
|
1697 | * @param {string} $2 - Text.
|
1698 | * @param {string?} $3 - URL wrapped in angle braces.
|
1699 | * @param {string?} $4 - Literal URL.
|
1700 | * @param {string?} $5 - Title wrapped in single or double
|
1701 | * quotes.
|
1702 | * @param {string?} [$6] - Title wrapped in double quotes.
|
1703 | * @param {string?} [$7] - Title wrapped in parentheses.
|
1704 | * @return {Node?} - `link` node, `image` node, or `null`.
|
1705 | */
|
1706 | function tokenizeLink(eat, $0, $1, $2, $3, $4, $5, $6, $7) {
|
1707 | var isLink = $1 === BRACKET_OPEN;
|
1708 | var href = $4 || $3 || '';
|
1709 | var title = $7 || $6 || $5;
|
1710 | var now;
|
1711 |
|
1712 | if (!isLink || !this.inLink) {
|
1713 | now = eat.now();
|
1714 |
|
1715 | now.column += $1.length;
|
1716 |
|
1717 | return eat($0)(this.renderLink(
|
1718 | isLink, this.descape(href), $2, title, now, eat
|
1719 | ));
|
1720 | }
|
1721 |
|
1722 | return null;
|
1723 | }
|
1724 |
|
1725 | /**
|
1726 | * Tokenise a reference link, image, or footnote;
|
1727 | * shortcut reference link, or footnote.
|
1728 | *
|
1729 | * @example
|
1730 | * tokenizeReference(eat, '[foo]', '[', 'foo');
|
1731 | * tokenizeReference(eat, '[foo][]', '[', 'foo', '');
|
1732 | * tokenizeReference(eat, '[foo][bar]', '[', 'foo', 'bar');
|
1733 | *
|
1734 | * @param {function(string)} eat
|
1735 | * @param {string} $0 - Whole link.
|
1736 | * @param {string} $1 - Prefix.
|
1737 | * @param {string} $2 - identifier.
|
1738 | * @param {string} $3 - Content.
|
1739 | * @return {Node?} - `linkReference`, `imageReference`, or
|
1740 | * `footnoteReference`. Returns null when this is a link
|
1741 | * reference, but we're already in a link.
|
1742 | */
|
1743 | function tokenizeReference(eat, $0, $1, $2, $3) {
|
1744 | var self = this;
|
1745 | var text = $2;
|
1746 | var identifier = $3 || $2;
|
1747 | var type = $1 === BRACKET_OPEN ? 'link' : 'image';
|
1748 | var isFootnote = self.options.footnotes && identifier.charAt(0) === CARET;
|
1749 | var now = eat.now();
|
1750 | var referenceType;
|
1751 | var node;
|
1752 | var exitLink;
|
1753 |
|
1754 | if ($3 === undefined) {
|
1755 | referenceType = 'shortcut';
|
1756 | } else if ($3 === '') {
|
1757 | referenceType = 'collapsed';
|
1758 | } else {
|
1759 | referenceType = 'full';
|
1760 | }
|
1761 |
|
1762 | if (referenceType !== 'shortcut') {
|
1763 | isFootnote = false;
|
1764 | }
|
1765 |
|
1766 | if (isFootnote) {
|
1767 | identifier = identifier.substr(1);
|
1768 | }
|
1769 |
|
1770 | if (isFootnote) {
|
1771 | if (identifier.indexOf(SPACE) !== -1) {
|
1772 | return eat($0)(self.renderFootnote(identifier, eat.now()));
|
1773 | } else {
|
1774 | type = 'footnote';
|
1775 | }
|
1776 | }
|
1777 |
|
1778 | if (self.inLink && type === 'link') {
|
1779 | return null;
|
1780 | }
|
1781 |
|
1782 | now.column += $1.length;
|
1783 |
|
1784 | node = {
|
1785 | 'type': type + 'Reference',
|
1786 | 'identifier': normalize(identifier)
|
1787 | };
|
1788 |
|
1789 | if (type === 'link' || type === 'image') {
|
1790 | node.referenceType = referenceType;
|
1791 | }
|
1792 |
|
1793 | if (type === 'link') {
|
1794 | exitLink = self.enterLink();
|
1795 | node.children = self.tokenizeInline(text, now);
|
1796 | exitLink();
|
1797 | } else if (type === 'image') {
|
1798 | node.alt = decode(self.descape(text), eat);
|
1799 | }
|
1800 |
|
1801 | return eat($0)(node);
|
1802 | }
|
1803 |
|
1804 | /**
|
1805 | * Tokenise strong emphasis.
|
1806 | *
|
1807 | * @example
|
1808 | * tokenizeStrong(eat, '**foo**', '**', 'foo');
|
1809 | * tokenizeStrong(eat, '__foo__', null, null, '__', 'foo');
|
1810 | *
|
1811 | * @param {function(string)} eat
|
1812 | * @param {string} $0 - Whole emphasis.
|
1813 | * @param {string?} $1 - Marker.
|
1814 | * @param {string?} $2 - Content.
|
1815 | * @param {string?} [$3] - Marker.
|
1816 | * @param {string?} [$4] - Content.
|
1817 | * @return {Node?} - `strong` node, when not empty.
|
1818 | */
|
1819 | function tokenizeStrong(eat, $0, $1, $2, $3, $4) {
|
1820 | var now = eat.now();
|
1821 | var value = $2 || $4;
|
1822 |
|
1823 | if (trim(value) === EMPTY) {
|
1824 | return null;
|
1825 | }
|
1826 |
|
1827 | now.column += 2;
|
1828 |
|
1829 | return eat($0)(this.renderInline(STRONG, value, now));
|
1830 | }
|
1831 |
|
1832 | /**
|
1833 | * Tokenise slight emphasis.
|
1834 | *
|
1835 | * @example
|
1836 | * tokenizeEmphasis(eat, '*foo*', '*', 'foo');
|
1837 | * tokenizeEmphasis(eat, '_foo_', null, null, '_', 'foo');
|
1838 | *
|
1839 | * @param {function(string)} eat
|
1840 | * @param {string} $0 - Whole emphasis.
|
1841 | * @param {string?} $1 - Marker.
|
1842 | * @param {string?} $2 - Content.
|
1843 | * @param {string?} [$3] - Marker.
|
1844 | * @param {string?} [$4] - Content.
|
1845 | * @return {Node?} - `emphasis` node, when not empty.
|
1846 | */
|
1847 | function tokenizeEmphasis(eat, $0, $1, $2, $3, $4) {
|
1848 | var now = eat.now();
|
1849 | var marker = $1 || $3;
|
1850 | var value = $2 || $4;
|
1851 |
|
1852 | if (
|
1853 | trim(value) === EMPTY ||
|
1854 | value.charAt(0) === marker ||
|
1855 | value.charAt(value.length - 1) === marker
|
1856 | ) {
|
1857 | return null;
|
1858 | }
|
1859 |
|
1860 | now.column += 1;
|
1861 |
|
1862 | return eat($0)(this.renderInline(EMPHASIS, value, now));
|
1863 | }
|
1864 |
|
1865 | /**
|
1866 | * Tokenise a deletion.
|
1867 | *
|
1868 | * @example
|
1869 | * tokenizeDeletion(eat, '~~foo~~', '~~', 'foo');
|
1870 | *
|
1871 | * @param {function(string)} eat
|
1872 | * @param {string} $0 - Whole deletion.
|
1873 | * @param {string} $1 - Content.
|
1874 | * @return {Node} - `delete` node.
|
1875 | */
|
1876 | function tokenizeDeletion(eat, $0, $1) {
|
1877 | var now = eat.now();
|
1878 |
|
1879 | now.column += 2;
|
1880 |
|
1881 | return eat($0)(this.renderInline(DELETE, $1, now));
|
1882 | }
|
1883 |
|
1884 | /**
|
1885 | * Tokenise inline code.
|
1886 | *
|
1887 | * @example
|
1888 | * tokenizeInlineCode(eat, '`foo()`', '`', 'foo()');
|
1889 | *
|
1890 | * @param {function(string)} eat
|
1891 | * @param {string} $0 - Whole code.
|
1892 | * @param {string} $1 - Initial markers.
|
1893 | * @param {string} $2 - Content.
|
1894 | * @return {Node} - `inlineCode` node.
|
1895 | */
|
1896 | function tokenizeInlineCode(eat, $0, $1, $2) {
|
1897 | return eat($0)(this.renderRaw(INLINE_CODE, trim($2 || '')));
|
1898 | }
|
1899 |
|
1900 | /**
|
1901 | * Tokenise a break.
|
1902 | *
|
1903 | * @example
|
1904 | * tokenizeBreak(eat, ' \n');
|
1905 | *
|
1906 | * @param {function(string)} eat
|
1907 | * @param {string} $0
|
1908 | * @return {Node} - `break` node.
|
1909 | */
|
1910 | function tokenizeBreak(eat, $0) {
|
1911 | return eat($0)(this.renderVoid(BREAK));
|
1912 | }
|
1913 |
|
1914 | /**
|
1915 | * Construct a new parser.
|
1916 | *
|
1917 | * @example
|
1918 | * var parser = new Parser(new VFile('Foo'));
|
1919 | *
|
1920 | * @constructor
|
1921 | * @class {Parser}
|
1922 | * @param {VFile} file - File to parse.
|
1923 | * @param {Object?} [options] - Passed to
|
1924 | * `Parser#setOptions()`.
|
1925 | */
|
1926 | function Parser(file, options) {
|
1927 | var self = this;
|
1928 | var rules = extend({}, self.expressions.rules);
|
1929 |
|
1930 | self.file = file;
|
1931 | self.inLink = false;
|
1932 | self.atTop = true;
|
1933 | self.atStart = true;
|
1934 | self.inBlockquote = false;
|
1935 |
|
1936 | self.rules = rules;
|
1937 | self.descape = descapeFactory(rules, 'escape');
|
1938 |
|
1939 | self.options = extend({}, self.options);
|
1940 |
|
1941 | self.setOptions(options);
|
1942 | }
|
1943 |
|
1944 | /**
|
1945 | * Set options. Does not overwrite previously set
|
1946 | * options.
|
1947 | *
|
1948 | * @example
|
1949 | * var parser = new Parser();
|
1950 | * parser.setOptions({gfm: true});
|
1951 | *
|
1952 | * @this {Parser}
|
1953 | * @throws {Error} - When an option is invalid.
|
1954 | * @param {Object?} [options] - Parse settings.
|
1955 | * @return {Parser} - `self`.
|
1956 | */
|
1957 | Parser.prototype.setOptions = function (options) {
|
1958 | var self = this;
|
1959 | var expressions = self.expressions;
|
1960 | var rules = self.rules;
|
1961 | var current = self.options;
|
1962 | var key;
|
1963 |
|
1964 | if (options === null || options === undefined) {
|
1965 | options = {};
|
1966 | } else if (typeof options === 'object') {
|
1967 | options = extend({}, options);
|
1968 | } else {
|
1969 | raise(options, 'options');
|
1970 | }
|
1971 |
|
1972 | self.options = options;
|
1973 |
|
1974 | for (key in defaultOptions) {
|
1975 | validate.boolean(options, key, current[key]);
|
1976 |
|
1977 | if (options[key]) {
|
1978 | extend(rules, expressions[key]);
|
1979 | }
|
1980 | }
|
1981 |
|
1982 | if (options.gfm && options.breaks) {
|
1983 | extend(rules, expressions.breaksGFM);
|
1984 | }
|
1985 |
|
1986 | if (options.gfm && options.commonmark) {
|
1987 | extend(rules, expressions.commonmarkGFM);
|
1988 | }
|
1989 |
|
1990 | if (options.commonmark) {
|
1991 | self.enterBlockquote = noopToggler();
|
1992 | }
|
1993 |
|
1994 | return self;
|
1995 | };
|
1996 |
|
1997 | /*
|
1998 | * Expose `defaults`.
|
1999 | */
|
2000 |
|
2001 | Parser.prototype.options = defaultOptions;
|
2002 |
|
2003 | /*
|
2004 | * Expose `expressions`.
|
2005 | */
|
2006 |
|
2007 | Parser.prototype.expressions = defaultExpressions;
|
2008 |
|
2009 | /**
|
2010 | * Factory to track indentation for each line corresponding
|
2011 | * to the given `start` and the number of invocations.
|
2012 | *
|
2013 | * @param {number} start - Starting line.
|
2014 | * @return {function(offset)} - Indenter.
|
2015 | */
|
2016 | Parser.prototype.indent = function (start) {
|
2017 | var self = this;
|
2018 | var line = start;
|
2019 |
|
2020 | /**
|
2021 | * Intender which increments the global offset,
|
2022 | * starting at the bound line, and further incrementing
|
2023 | * each line for each invocation.
|
2024 | *
|
2025 | * @example
|
2026 | * indenter(2)
|
2027 | *
|
2028 | * @param {number} offset - Number to increment the
|
2029 | * offset.
|
2030 | */
|
2031 | function indenter(offset) {
|
2032 | self.offset[line] = (self.offset[line] || 0) + offset;
|
2033 |
|
2034 | line++;
|
2035 | }
|
2036 |
|
2037 | return indenter;
|
2038 | };
|
2039 |
|
2040 | /**
|
2041 | * Parse the bound file.
|
2042 | *
|
2043 | * @example
|
2044 | * new Parser(new File('_Foo_.')).parse();
|
2045 | *
|
2046 | * @this {Parser}
|
2047 | * @return {Object} - `root` node.
|
2048 | */
|
2049 | Parser.prototype.parse = function () {
|
2050 | var self = this;
|
2051 | var value = clean(String(self.file));
|
2052 | var node;
|
2053 |
|
2054 | /*
|
2055 | * Add an `offset` matrix, used to keep track of
|
2056 | * syntax and white space indentation per line.
|
2057 | */
|
2058 |
|
2059 | self.offset = {};
|
2060 |
|
2061 | node = self.renderBlock(ROOT, value);
|
2062 |
|
2063 | if (self.options.position) {
|
2064 | node.position = {
|
2065 | 'start': {
|
2066 | 'line': 1,
|
2067 | 'column': 1
|
2068 | }
|
2069 | };
|
2070 |
|
2071 | node.position.end = self.eof || node.position.start;
|
2072 | }
|
2073 |
|
2074 | return node;
|
2075 | };
|
2076 |
|
2077 | /*
|
2078 | * Enter and exit helpers.
|
2079 | */
|
2080 |
|
2081 | Parser.prototype.enterLink = stateToggler('inLink', false);
|
2082 | Parser.prototype.exitTop = stateToggler('atTop', true);
|
2083 | Parser.prototype.exitStart = stateToggler('atStart', true);
|
2084 | Parser.prototype.enterBlockquote = stateToggler('inBlockquote', false);
|
2085 |
|
2086 | /*
|
2087 | * Expose helpers
|
2088 | */
|
2089 |
|
2090 | Parser.prototype.renderRaw = renderRaw;
|
2091 | Parser.prototype.renderVoid = renderVoid;
|
2092 | Parser.prototype.renderParent = renderParent;
|
2093 | Parser.prototype.renderInline = renderInline;
|
2094 | Parser.prototype.renderBlock = renderBlock;
|
2095 |
|
2096 | Parser.prototype.renderLink = renderLink;
|
2097 | Parser.prototype.renderCodeBlock = renderCodeBlock;
|
2098 | Parser.prototype.renderBlockquote = renderBlockquote;
|
2099 | Parser.prototype.renderList = renderList;
|
2100 | Parser.prototype.renderListItem = renderListItem;
|
2101 | Parser.prototype.renderFootnoteDefinition = renderFootnoteDefinition;
|
2102 | Parser.prototype.renderHeading = renderHeading;
|
2103 | Parser.prototype.renderFootnote = renderFootnote;
|
2104 |
|
2105 | /**
|
2106 | * Construct a tokenizer. This creates both
|
2107 | * `tokenizeInline` and `tokenizeBlock`.
|
2108 | *
|
2109 | * @example
|
2110 | * Parser.prototype.tokenizeInline = tokenizeFactory('inline');
|
2111 | *
|
2112 | * @param {string} type - Name of parser, used to find
|
2113 | * its expressions (`%sMethods`) and tokenizers
|
2114 | * (`%Tokenizers`).
|
2115 | * @return {function(string, Object?): Array.<Object>}
|
2116 | */
|
2117 | function tokenizeFactory(type) {
|
2118 | /**
|
2119 | * Tokenizer for a bound `type`
|
2120 | *
|
2121 | * @example
|
2122 | * parser = new Parser();
|
2123 | * parser.tokenizeInline('_foo_');
|
2124 | *
|
2125 | * @param {string} value - Content.
|
2126 | * @param {Object?} [location] - Offset at which `value`
|
2127 | * starts.
|
2128 | * @return {Array.<Object>} - Nodes.
|
2129 | */
|
2130 | function tokenize(value, location) {
|
2131 | var self = this;
|
2132 | var offset = self.offset;
|
2133 | var tokens = [];
|
2134 | var rules = self.rules;
|
2135 | var methods = self[type + 'Methods'];
|
2136 | var tokenizers = self[type + 'Tokenizers'];
|
2137 | var line = location ? location.line : 1;
|
2138 | var column = location ? location.column : 1;
|
2139 | var patchPosition = self.options.position;
|
2140 | var add;
|
2141 | var index;
|
2142 | var length;
|
2143 | var method;
|
2144 | var name;
|
2145 | var match;
|
2146 | var matched;
|
2147 | var valueLength;
|
2148 | var eater;
|
2149 |
|
2150 | /*
|
2151 | * Trim white space only lines.
|
2152 | */
|
2153 |
|
2154 | if (!value) {
|
2155 | return tokens;
|
2156 | }
|
2157 |
|
2158 | /**
|
2159 | * Update line and column based on `value`.
|
2160 | *
|
2161 | * @example
|
2162 | * updatePosition('foo');
|
2163 | *
|
2164 | * @param {string} subvalue
|
2165 | */
|
2166 | function updatePosition(subvalue) {
|
2167 | var character = -1;
|
2168 | var subvalueLength = subvalue.length;
|
2169 | var lastIndex = -1;
|
2170 |
|
2171 | while (++character < subvalueLength) {
|
2172 | if (subvalue.charAt(character) === NEW_LINE) {
|
2173 | lastIndex = character;
|
2174 | line++;
|
2175 | }
|
2176 | }
|
2177 |
|
2178 | if (lastIndex === -1) {
|
2179 | column = column + subvalue.length;
|
2180 | } else {
|
2181 | column = subvalue.length - lastIndex;
|
2182 | }
|
2183 |
|
2184 | if (line in offset) {
|
2185 | if (lastIndex !== -1) {
|
2186 | column += offset[line];
|
2187 | } else if (column <= offset[line]) {
|
2188 | column = offset[line] + 1;
|
2189 | }
|
2190 | }
|
2191 | }
|
2192 |
|
2193 | /**
|
2194 | * Get offset. Called before the fisrt character is
|
2195 | * eaten to retrieve the range's offsets.
|
2196 | *
|
2197 | * @return {Function} - `done`, to be called when
|
2198 | * the last character is eaten.
|
2199 | */
|
2200 | function getOffset() {
|
2201 | var indentation = [];
|
2202 | var pos = line + 1;
|
2203 |
|
2204 | /**
|
2205 | * Done. Called when the last character is
|
2206 | * eaten to retrieve the range's offsets.
|
2207 | *
|
2208 | * @return {Array.<number>} - Offset.
|
2209 | */
|
2210 | function done() {
|
2211 | var last = line + 1;
|
2212 |
|
2213 | while (pos < last) {
|
2214 | indentation.push((offset[pos] || 0) + 1);
|
2215 |
|
2216 | pos++;
|
2217 | }
|
2218 |
|
2219 | return indentation;
|
2220 | }
|
2221 |
|
2222 | return done;
|
2223 | }
|
2224 |
|
2225 | /**
|
2226 | * Get the current position.
|
2227 | *
|
2228 | * @example
|
2229 | * position = now(); // {line: 1, column: 1}
|
2230 | *
|
2231 | * @return {Object}
|
2232 | */
|
2233 | function now() {
|
2234 | return {
|
2235 | 'line': line,
|
2236 | 'column': column
|
2237 | };
|
2238 | }
|
2239 |
|
2240 | /**
|
2241 | * Store position information for a node.
|
2242 | *
|
2243 | * @example
|
2244 | * start = now();
|
2245 | * updatePosition('foo');
|
2246 | * location = new Position(start);
|
2247 | * // {start: {line: 1, column: 1}, end: {line: 1, column: 3}}
|
2248 | *
|
2249 | * @param {Object} start
|
2250 | */
|
2251 | function Position(start) {
|
2252 | this.start = start;
|
2253 | this.end = now();
|
2254 | }
|
2255 |
|
2256 | /**
|
2257 | * Throw when a value is incorrectly eaten.
|
2258 | * This shouldn’t happen but will throw on new,
|
2259 | * incorrect rules.
|
2260 | *
|
2261 | * @example
|
2262 | * // When the current value is set to `foo bar`.
|
2263 | * validateEat('foo');
|
2264 | * eat('foo');
|
2265 | *
|
2266 | * validateEat('bar');
|
2267 | * // throws, because the space is not eaten.
|
2268 | *
|
2269 | * @param {string} subvalue - Value to be eaten.
|
2270 | * @throws {Error} - When `subvalue` cannot be eaten.
|
2271 | */
|
2272 | function validateEat(subvalue) {
|
2273 | /* istanbul ignore if */
|
2274 | if (value.substring(0, subvalue.length) !== subvalue) {
|
2275 | self.file.fail(
|
2276 | 'Incorrectly eaten value: please report this ' +
|
2277 | 'warning on http://git.io/vUYWz', now()
|
2278 | );
|
2279 | }
|
2280 | }
|
2281 |
|
2282 | /**
|
2283 | * Mark position and patch `node.position`.
|
2284 | *
|
2285 | * @example
|
2286 | * var update = position();
|
2287 | * updatePosition('foo');
|
2288 | * update({});
|
2289 | * // {
|
2290 | * // position: {
|
2291 | * // start: {line: 1, column: 1}
|
2292 | * // end: {line: 1, column: 3}
|
2293 | * // }
|
2294 | * // }
|
2295 | *
|
2296 | * @returns {function(Node): Node}
|
2297 | */
|
2298 | function position() {
|
2299 | var before = now();
|
2300 |
|
2301 | /**
|
2302 | * Add the position to a node.
|
2303 | *
|
2304 | * @example
|
2305 | * update({type: 'text', value: 'foo'});
|
2306 | *
|
2307 | * @param {Node} node - Node to attach position
|
2308 | * on.
|
2309 | * @return {Node} - `node`.
|
2310 | */
|
2311 | function update(node, indent) {
|
2312 | var prev = node.position;
|
2313 | var start = prev ? prev.start : before;
|
2314 | var combined = [];
|
2315 | var n = prev && prev.end.line;
|
2316 | var l = before.line;
|
2317 |
|
2318 | node.position = new Position(start);
|
2319 |
|
2320 | /*
|
2321 | * If there was already a `position`, this
|
2322 | * node was merged. Fixing `start` wasn't
|
2323 | * hard, but the indent is different.
|
2324 | * Especially because some information, the
|
2325 | * indent between `n` and `l` wasn't
|
2326 | * tracked. Luckily, that space is
|
2327 | * (should be?) empty, so we can safely
|
2328 | * check for it now.
|
2329 | */
|
2330 |
|
2331 | if (prev) {
|
2332 | combined = prev.indent;
|
2333 |
|
2334 | if (n < l) {
|
2335 | while (++n < l) {
|
2336 | combined.push((offset[n] || 0) + 1);
|
2337 | }
|
2338 |
|
2339 | combined.push(before.column);
|
2340 | }
|
2341 |
|
2342 | indent = combined.concat(indent);
|
2343 | }
|
2344 |
|
2345 | node.position.indent = indent;
|
2346 |
|
2347 | return node;
|
2348 | }
|
2349 |
|
2350 | return update;
|
2351 | }
|
2352 |
|
2353 | /**
|
2354 | * Add `node` to `parent`s children or to `tokens`.
|
2355 | * Performs merges where possible.
|
2356 | *
|
2357 | * @example
|
2358 | * add({});
|
2359 | *
|
2360 | * add({}, {children: []});
|
2361 | *
|
2362 | * @param {Object} node - Node to add.
|
2363 | * @param {Object} [parent] - Parent to insert into.
|
2364 | * @return {Object} - Added or merged into node.
|
2365 | */
|
2366 | add = function (node, parent) {
|
2367 | var isMultiple = 'length' in node;
|
2368 | var prev;
|
2369 | var children;
|
2370 |
|
2371 | if (!parent) {
|
2372 | children = tokens;
|
2373 | } else {
|
2374 | children = parent.children;
|
2375 | }
|
2376 |
|
2377 | if (isMultiple) {
|
2378 | arrayPush.apply(children, node);
|
2379 | } else {
|
2380 | if (type === INLINE && node.type === TEXT) {
|
2381 | node.value = decode(node.value, eater);
|
2382 | }
|
2383 |
|
2384 | prev = children[children.length - 1];
|
2385 |
|
2386 | if (
|
2387 | prev &&
|
2388 | node.type === prev.type &&
|
2389 | node.type in MERGEABLE_NODES
|
2390 | ) {
|
2391 | node = MERGEABLE_NODES[node.type].call(
|
2392 | self, prev, node
|
2393 | );
|
2394 | }
|
2395 |
|
2396 | if (node !== prev) {
|
2397 | children.push(node);
|
2398 | }
|
2399 |
|
2400 | if (self.atStart && tokens.length) {
|
2401 | self.exitStart();
|
2402 | }
|
2403 | }
|
2404 |
|
2405 | return node;
|
2406 | };
|
2407 |
|
2408 | /**
|
2409 | * Remove `subvalue` from `value`.
|
2410 | * Expects `subvalue` to be at the start from
|
2411 | * `value`, and applies no validation.
|
2412 | *
|
2413 | * @example
|
2414 | * eat('foo')({type: 'text', value: 'foo'});
|
2415 | *
|
2416 | * @param {string} subvalue - Removed from `value`,
|
2417 | * and passed to `updatePosition`.
|
2418 | * @return {Function} - Wrapper around `add`, which
|
2419 | * also adds `position` to node.
|
2420 | */
|
2421 | function eat(subvalue) {
|
2422 | var indent = getOffset();
|
2423 | var pos = position();
|
2424 | var current = now();
|
2425 |
|
2426 | validateEat(subvalue);
|
2427 |
|
2428 | /**
|
2429 | * Add the given arguments, add `position` to
|
2430 | * the returned node, and return the node.
|
2431 | *
|
2432 | * @return {Node}
|
2433 | */
|
2434 | function apply() {
|
2435 | return pos(add.apply(null, arguments), indent);
|
2436 | }
|
2437 |
|
2438 | /**
|
2439 | * Functions just like apply, but resets the
|
2440 | * content: the line and column are reversed,
|
2441 | * and the eaten value is re-added.
|
2442 | *
|
2443 | * This is useful for nodes with a single
|
2444 | * type of content, such as lists and tables.
|
2445 | *
|
2446 | * See `apply` above for what parameters are
|
2447 | * expected.
|
2448 | *
|
2449 | * @return {Node}
|
2450 | */
|
2451 | function reset() {
|
2452 | var node = apply.apply(null, arguments);
|
2453 |
|
2454 | line = current.line;
|
2455 | column = current.column;
|
2456 | value = subvalue + value;
|
2457 |
|
2458 | return node;
|
2459 | }
|
2460 |
|
2461 | apply.reset = reset;
|
2462 |
|
2463 | value = value.substring(subvalue.length);
|
2464 |
|
2465 | updatePosition(subvalue);
|
2466 |
|
2467 | indent = indent();
|
2468 |
|
2469 | return apply;
|
2470 | }
|
2471 |
|
2472 | /**
|
2473 | * Same as `eat` above, but will not add positional
|
2474 | * information to nodes.
|
2475 | *
|
2476 | * @example
|
2477 | * noEat('foo')({type: 'text', value: 'foo'});
|
2478 | *
|
2479 | * @param {string} subvalue - Removed from `value`.
|
2480 | * @return {Function} - Wrapper around `add`.
|
2481 | */
|
2482 | function noEat(subvalue) {
|
2483 | validateEat(subvalue);
|
2484 |
|
2485 | /**
|
2486 | * Add the given arguments, and return the
|
2487 | * node.
|
2488 | *
|
2489 | * @return {Node}
|
2490 | */
|
2491 | function apply() {
|
2492 | return add.apply(null, arguments);
|
2493 | }
|
2494 |
|
2495 | /**
|
2496 | * Functions just like apply, but resets the
|
2497 | * content: the eaten value is re-added.
|
2498 | *
|
2499 | * @return {Node}
|
2500 | */
|
2501 | function reset() {
|
2502 | var node = apply.apply(null, arguments);
|
2503 |
|
2504 | value = subvalue + value;
|
2505 |
|
2506 | return node;
|
2507 | }
|
2508 |
|
2509 | apply.reset = reset;
|
2510 |
|
2511 | value = value.substring(subvalue.length);
|
2512 |
|
2513 | return apply;
|
2514 | }
|
2515 |
|
2516 | /*
|
2517 | * Expose the eater, depending on if `position`s
|
2518 | * should be patched on nodes.
|
2519 | */
|
2520 |
|
2521 | eater = patchPosition ? eat : noEat;
|
2522 |
|
2523 | /*
|
2524 | * Expose `now` on `eater`.
|
2525 | */
|
2526 |
|
2527 | eater.now = now;
|
2528 |
|
2529 | /*
|
2530 | * Expose `file` on `eater`.
|
2531 | */
|
2532 |
|
2533 | eater.file = self.file;
|
2534 |
|
2535 | /*
|
2536 | * Sync initial offset.
|
2537 | */
|
2538 |
|
2539 | updatePosition(EMPTY);
|
2540 |
|
2541 | /*
|
2542 | * Iterate over `value`, and iterate over all
|
2543 | * block-expressions. When one matches, invoke
|
2544 | * its companion function. If no expression
|
2545 | * matches, something failed (should not happen)
|
2546 | * and an exception is thrown.
|
2547 | */
|
2548 |
|
2549 | while (value) {
|
2550 | index = -1;
|
2551 | length = methods.length;
|
2552 | matched = false;
|
2553 |
|
2554 | while (++index < length) {
|
2555 | name = methods[index];
|
2556 | method = tokenizers[name];
|
2557 |
|
2558 | if (
|
2559 | method &&
|
2560 | rules[name] &&
|
2561 | (!method.onlyAtStart || self.atStart) &&
|
2562 | (!method.onlyAtTop || self.atTop) &&
|
2563 | (!method.notInBlockquote || !self.inBlockquote) &&
|
2564 | (!method.notInLink || !self.inLink)
|
2565 | ) {
|
2566 | match = rules[name].exec(value);
|
2567 |
|
2568 | if (match) {
|
2569 | valueLength = value.length;
|
2570 |
|
2571 | method.apply(self, [eater].concat(match));
|
2572 |
|
2573 | matched = valueLength !== value.length;
|
2574 |
|
2575 | if (matched) {
|
2576 | break;
|
2577 | }
|
2578 | }
|
2579 | }
|
2580 | }
|
2581 |
|
2582 | /* istanbul ignore if */
|
2583 | if (!matched) {
|
2584 | self.file.fail('Infinite loop', eater.now());
|
2585 |
|
2586 | /*
|
2587 | * Errors are not thrown on `File#fail`
|
2588 | * when `quiet: true`.
|
2589 | */
|
2590 |
|
2591 | break;
|
2592 | }
|
2593 | }
|
2594 |
|
2595 | self.eof = now();
|
2596 |
|
2597 | return tokens;
|
2598 | }
|
2599 |
|
2600 | return tokenize;
|
2601 | }
|
2602 |
|
2603 | /*
|
2604 | * Expose tokenizers for block-level nodes.
|
2605 | */
|
2606 |
|
2607 | Parser.prototype.blockTokenizers = {
|
2608 | 'yamlFrontMatter': tokenizeYAMLFrontMatter,
|
2609 | 'newline': tokenizeNewline,
|
2610 | 'code': tokenizeCode,
|
2611 | 'fences': tokenizeFences,
|
2612 | 'heading': tokenizeHeading,
|
2613 | 'lineHeading': tokenizeLineHeading,
|
2614 | 'horizontalRule': tokenizeHorizontalRule,
|
2615 | 'blockquote': tokenizeBlockquote,
|
2616 | 'list': tokenizeList,
|
2617 | 'html': tokenizeHtml,
|
2618 | 'definition': tokenizeDefinition,
|
2619 | 'footnoteDefinition': tokenizeFootnoteDefinition,
|
2620 | 'looseTable': tokenizeTable,
|
2621 | 'table': tokenizeTable,
|
2622 | 'paragraph': tokenizeParagraph
|
2623 | };
|
2624 |
|
2625 | /*
|
2626 | * Expose order in which to parse block-level nodes.
|
2627 | */
|
2628 |
|
2629 | Parser.prototype.blockMethods = [
|
2630 | 'yamlFrontMatter',
|
2631 | 'newline',
|
2632 | 'code',
|
2633 | 'fences',
|
2634 | 'blockquote',
|
2635 | 'heading',
|
2636 | 'horizontalRule',
|
2637 | 'list',
|
2638 | 'lineHeading',
|
2639 | 'html',
|
2640 | 'definition',
|
2641 | 'footnoteDefinition',
|
2642 | 'looseTable',
|
2643 | 'table',
|
2644 | 'paragraph'
|
2645 | ];
|
2646 |
|
2647 | /**
|
2648 | * Block tokenizer.
|
2649 | *
|
2650 | * @example
|
2651 | * var parser = new Parser();
|
2652 | * parser.tokenizeBlock('> foo.');
|
2653 | *
|
2654 | * @param {string} value - Content.
|
2655 | * @return {Array.<Object>} - Nodes.
|
2656 | */
|
2657 |
|
2658 | Parser.prototype.tokenizeBlock = tokenizeFactory(BLOCK);
|
2659 |
|
2660 | /*
|
2661 | * Expose tokenizers for inline-level nodes.
|
2662 | */
|
2663 |
|
2664 | Parser.prototype.inlineTokenizers = {
|
2665 | 'escape': tokenizeEscape,
|
2666 | 'autoLink': tokenizeAutoLink,
|
2667 | 'url': tokenizeURL,
|
2668 | 'tag': tokenizeTag,
|
2669 | 'link': tokenizeLink,
|
2670 | 'reference': tokenizeReference,
|
2671 | 'shortcutReference': tokenizeReference,
|
2672 | 'strong': tokenizeStrong,
|
2673 | 'emphasis': tokenizeEmphasis,
|
2674 | 'deletion': tokenizeDeletion,
|
2675 | 'inlineCode': tokenizeInlineCode,
|
2676 | 'break': tokenizeBreak,
|
2677 | 'inlineText': tokenizeText
|
2678 | };
|
2679 |
|
2680 | /*
|
2681 | * Expose order in which to parse inline-level nodes.
|
2682 | */
|
2683 |
|
2684 | Parser.prototype.inlineMethods = [
|
2685 | 'escape',
|
2686 | 'autoLink',
|
2687 | 'url',
|
2688 | 'tag',
|
2689 | 'link',
|
2690 | 'reference',
|
2691 | 'shortcutReference',
|
2692 | 'strong',
|
2693 | 'emphasis',
|
2694 | 'deletion',
|
2695 | 'inlineCode',
|
2696 | 'break',
|
2697 | 'inlineText'
|
2698 | ];
|
2699 |
|
2700 | /**
|
2701 | * Inline tokenizer.
|
2702 | *
|
2703 | * @example
|
2704 | * var parser = new Parser();
|
2705 | * parser.tokenizeInline('_foo_');
|
2706 | *
|
2707 | * @param {string} value - Content.
|
2708 | * @return {Array.<Object>} - Nodes.
|
2709 | */
|
2710 |
|
2711 | Parser.prototype.tokenizeInline = tokenizeFactory(INLINE);
|
2712 |
|
2713 | /*
|
2714 | * Expose `tokenizeFactory` so dependencies could create
|
2715 | * their own tokenizers.
|
2716 | */
|
2717 |
|
2718 | Parser.prototype.tokenizeFactory = tokenizeFactory;
|
2719 |
|
2720 | /*
|
2721 | * Expose `parse` on `module.exports`.
|
2722 | */
|
2723 |
|
2724 | module.exports = Parser;
|