UNPKG

5.12 kBJavaScriptView Raw
1const Renderer = require('./Renderer.js');
2const Slugger = require('./Slugger.js');
3const InlineLexer = require('./InlineLexer.js');
4const TextRenderer = require('./TextRenderer.js');
5const { defaults } = require('./defaults.js');
6const {
7 merge,
8 unescape
9} = require('./helpers.js');
10
11/**
12 * Parsing & Compiling
13 */
14module.exports = class Parser {
15 constructor(options) {
16 this.tokens = [];
17 this.token = null;
18 this.options = options || defaults;
19 this.options.renderer = this.options.renderer || new Renderer();
20 this.renderer = this.options.renderer;
21 this.renderer.options = this.options;
22 this.slugger = new Slugger();
23 }
24
25 /**
26 * Static Parse Method
27 */
28 static parse(tokens, options) {
29 const parser = new Parser(options);
30 return parser.parse(tokens);
31 };
32
33 /**
34 * Parse Loop
35 */
36 parse(tokens) {
37 this.inline = new InlineLexer(tokens.links, this.options);
38 // use an InlineLexer with a TextRenderer to extract pure text
39 this.inlineText = new InlineLexer(
40 tokens.links,
41 merge({}, this.options, { renderer: new TextRenderer() })
42 );
43 this.tokens = tokens.reverse();
44
45 let out = '';
46 while (this.next()) {
47 out += this.tok();
48 }
49
50 return out;
51 };
52
53 /**
54 * Next Token
55 */
56 next() {
57 this.token = this.tokens.pop();
58 return this.token;
59 };
60
61 /**
62 * Preview Next Token
63 */
64 peek() {
65 return this.tokens[this.tokens.length - 1] || 0;
66 };
67
68 /**
69 * Parse Text Tokens
70 */
71 parseText() {
72 let body = this.token.text;
73
74 while (this.peek().type === 'text') {
75 body += '\n' + this.next().text;
76 }
77
78 return this.inline.output(body);
79 };
80
81 /**
82 * Parse Current Token
83 */
84 tok() {
85 let body = '';
86 switch (this.token.type) {
87 case 'space': {
88 return '';
89 }
90 case 'hr': {
91 return this.renderer.hr();
92 }
93 case 'heading': {
94 return this.renderer.heading(
95 this.inline.output(this.token.text),
96 this.token.depth,
97 unescape(this.inlineText.output(this.token.text)),
98 this.slugger);
99 }
100 case 'code': {
101 return this.renderer.code(this.token.text,
102 this.token.lang,
103 this.token.escaped);
104 }
105 case 'table': {
106 let header = '',
107 i,
108 row,
109 cell,
110 j;
111
112 // header
113 cell = '';
114 for (i = 0; i < this.token.header.length; i++) {
115 cell += this.renderer.tablecell(
116 this.inline.output(this.token.header[i]),
117 { header: true, align: this.token.align[i] }
118 );
119 }
120 header += this.renderer.tablerow(cell);
121
122 for (i = 0; i < this.token.cells.length; i++) {
123 row = this.token.cells[i];
124
125 cell = '';
126 for (j = 0; j < row.length; j++) {
127 cell += this.renderer.tablecell(
128 this.inline.output(row[j]),
129 { header: false, align: this.token.align[j] }
130 );
131 }
132
133 body += this.renderer.tablerow(cell);
134 }
135 return this.renderer.table(header, body);
136 }
137 case 'blockquote_start': {
138 body = '';
139
140 while (this.next().type !== 'blockquote_end') {
141 body += this.tok();
142 }
143
144 return this.renderer.blockquote(body);
145 }
146 case 'list_start': {
147 body = '';
148 const ordered = this.token.ordered,
149 start = this.token.start;
150
151 while (this.next().type !== 'list_end') {
152 body += this.tok();
153 }
154
155 return this.renderer.list(body, ordered, start);
156 }
157 case 'list_item_start': {
158 body = '';
159 const loose = this.token.loose;
160 const checked = this.token.checked;
161 const task = this.token.task;
162
163 if (this.token.task) {
164 if (loose) {
165 if (this.peek().type === 'text') {
166 const nextToken = this.peek();
167 nextToken.text = this.renderer.checkbox(checked) + ' ' + nextToken.text;
168 } else {
169 this.tokens.push({
170 type: 'text',
171 text: this.renderer.checkbox(checked)
172 });
173 }
174 } else {
175 body += this.renderer.checkbox(checked);
176 }
177 }
178
179 while (this.next().type !== 'list_item_end') {
180 body += !loose && this.token.type === 'text'
181 ? this.parseText()
182 : this.tok();
183 }
184 return this.renderer.listitem(body, task, checked);
185 }
186 case 'html': {
187 // TODO parse inline content if parameter markdown=1
188 return this.renderer.html(this.token.text);
189 }
190 case 'paragraph': {
191 return this.renderer.paragraph(this.inline.output(this.token.text));
192 }
193 case 'text': {
194 return this.renderer.paragraph(this.parseText());
195 }
196 default: {
197 const errMsg = 'Token with "' + this.token.type + '" type was not found.';
198 if (this.options.silent) {
199 console.log(errMsg);
200 } else {
201 throw new Error(errMsg);
202 }
203 }
204 }
205 };
206};