UNPKG

7.92 kBJavaScriptView Raw
1const Renderer = require('./Renderer.js');
2const TextRenderer = require('./TextRenderer.js');
3const Slugger = require('./Slugger.js');
4const { defaults } = require('./defaults.js');
5const {
6 unescape
7} = require('./helpers.js');
8
9/**
10 * Parsing & Compiling
11 */
12module.exports = class Parser {
13 constructor(options) {
14 this.options = options || defaults;
15 this.options.renderer = this.options.renderer || new Renderer();
16 this.renderer = this.options.renderer;
17 this.renderer.options = this.options;
18 this.textRenderer = new TextRenderer();
19 this.slugger = new Slugger();
20 }
21
22 /**
23 * Static Parse Method
24 */
25 static parse(tokens, options) {
26 const parser = new Parser(options);
27 return parser.parse(tokens);
28 }
29
30 /**
31 * Static Parse Inline Method
32 */
33 static parseInline(tokens, options) {
34 const parser = new Parser(options);
35 return parser.parseInline(tokens);
36 }
37
38 /**
39 * Parse Loop
40 */
41 parse(tokens, top = true) {
42 let out = '',
43 i,
44 j,
45 k,
46 l2,
47 l3,
48 row,
49 cell,
50 header,
51 body,
52 token,
53 ordered,
54 start,
55 loose,
56 itemBody,
57 item,
58 checked,
59 task,
60 checkbox,
61 ret;
62
63 const l = tokens.length;
64 for (i = 0; i < l; i++) {
65 token = tokens[i];
66
67 // Run any renderer extensions
68 if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
69 ret = this.options.extensions.renderers[token.type].call(this, token);
70 if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(token.type)) {
71 out += ret || '';
72 continue;
73 }
74 }
75
76 switch (token.type) {
77 case 'space': {
78 continue;
79 }
80 case 'hr': {
81 out += this.renderer.hr();
82 continue;
83 }
84 case 'heading': {
85 out += this.renderer.heading(
86 this.parseInline(token.tokens),
87 token.depth,
88 unescape(this.parseInline(token.tokens, this.textRenderer)),
89 this.slugger);
90 continue;
91 }
92 case 'code': {
93 out += this.renderer.code(token.text,
94 token.lang,
95 token.escaped);
96 continue;
97 }
98 case 'table': {
99 header = '';
100
101 // header
102 cell = '';
103 l2 = token.header.length;
104 for (j = 0; j < l2; j++) {
105 cell += this.renderer.tablecell(
106 this.parseInline(token.tokens.header[j]),
107 { header: true, align: token.align[j] }
108 );
109 }
110 header += this.renderer.tablerow(cell);
111
112 body = '';
113 l2 = token.cells.length;
114 for (j = 0; j < l2; j++) {
115 row = token.tokens.cells[j];
116
117 cell = '';
118 l3 = row.length;
119 for (k = 0; k < l3; k++) {
120 cell += this.renderer.tablecell(
121 this.parseInline(row[k]),
122 { header: false, align: token.align[k] }
123 );
124 }
125
126 body += this.renderer.tablerow(cell);
127 }
128 out += this.renderer.table(header, body);
129 continue;
130 }
131 case 'blockquote': {
132 body = this.parse(token.tokens);
133 out += this.renderer.blockquote(body);
134 continue;
135 }
136 case 'list': {
137 ordered = token.ordered;
138 start = token.start;
139 loose = token.loose;
140 l2 = token.items.length;
141
142 body = '';
143 for (j = 0; j < l2; j++) {
144 item = token.items[j];
145 checked = item.checked;
146 task = item.task;
147
148 itemBody = '';
149 if (item.task) {
150 checkbox = this.renderer.checkbox(checked);
151 if (loose) {
152 if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
153 item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
154 if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
155 item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
156 }
157 } else {
158 item.tokens.unshift({
159 type: 'text',
160 text: checkbox
161 });
162 }
163 } else {
164 itemBody += checkbox;
165 }
166 }
167
168 itemBody += this.parse(item.tokens, loose);
169 body += this.renderer.listitem(itemBody, task, checked);
170 }
171
172 out += this.renderer.list(body, ordered, start);
173 continue;
174 }
175 case 'html': {
176 // TODO parse inline content if parameter markdown=1
177 out += this.renderer.html(token.text);
178 continue;
179 }
180 case 'paragraph': {
181 out += this.renderer.paragraph(this.parseInline(token.tokens));
182 continue;
183 }
184 case 'text': {
185 body = token.tokens ? this.parseInline(token.tokens) : token.text;
186 while (i + 1 < l && tokens[i + 1].type === 'text') {
187 token = tokens[++i];
188 body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
189 }
190 out += top ? this.renderer.paragraph(body) : body;
191 continue;
192 }
193
194 default: {
195 const errMsg = 'Token with "' + token.type + '" type was not found.';
196 if (this.options.silent) {
197 console.error(errMsg);
198 return;
199 } else {
200 throw new Error(errMsg);
201 }
202 }
203 }
204 }
205
206 return out;
207 }
208
209 /**
210 * Parse Inline Tokens
211 */
212 parseInline(tokens, renderer) {
213 renderer = renderer || this.renderer;
214 let out = '',
215 i,
216 token,
217 ret;
218
219 const l = tokens.length;
220 for (i = 0; i < l; i++) {
221 token = tokens[i];
222
223 // Run any renderer extensions
224 if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
225 ret = this.options.extensions.renderers[token.type].call(this, token);
226 if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) {
227 out += ret || '';
228 continue;
229 }
230 }
231
232 switch (token.type) {
233 case 'escape': {
234 out += renderer.text(token.text);
235 break;
236 }
237 case 'html': {
238 out += renderer.html(token.text);
239 break;
240 }
241 case 'link': {
242 out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
243 break;
244 }
245 case 'image': {
246 out += renderer.image(token.href, token.title, token.text);
247 break;
248 }
249 case 'strong': {
250 out += renderer.strong(this.parseInline(token.tokens, renderer));
251 break;
252 }
253 case 'em': {
254 out += renderer.em(this.parseInline(token.tokens, renderer));
255 break;
256 }
257 case 'codespan': {
258 out += renderer.codespan(token.text);
259 break;
260 }
261 case 'br': {
262 out += renderer.br();
263 break;
264 }
265 case 'del': {
266 out += renderer.del(this.parseInline(token.tokens, renderer));
267 break;
268 }
269 case 'text': {
270 out += renderer.text(token.text);
271 break;
272 }
273 default: {
274 const errMsg = 'Token with "' + token.type + '" type was not found.';
275 if (this.options.silent) {
276 console.error(errMsg);
277 return;
278 } else {
279 throw new Error(errMsg);
280 }
281 }
282 }
283 }
284 return out;
285 }
286};