Parser.js (7000B)
1 const Renderer = require('./Renderer.js'); 2 const TextRenderer = require('./TextRenderer.js'); 3 const Slugger = require('./Slugger.js'); 4 const { defaults } = require('./defaults.js'); 5 const { 6 unescape 7 } = require('./helpers.js'); 8 9 /** 10 * Parsing & Compiling 11 */ 12 module.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 62 const l = tokens.length; 63 for (i = 0; i < l; i++) { 64 token = tokens[i]; 65 switch (token.type) { 66 case 'space': { 67 continue; 68 } 69 case 'hr': { 70 out += this.renderer.hr(); 71 continue; 72 } 73 case 'heading': { 74 out += this.renderer.heading( 75 this.parseInline(token.tokens), 76 token.depth, 77 unescape(this.parseInline(token.tokens, this.textRenderer)), 78 this.slugger); 79 continue; 80 } 81 case 'code': { 82 out += this.renderer.code(token.text, 83 token.lang, 84 token.escaped); 85 continue; 86 } 87 case 'table': { 88 header = ''; 89 90 // header 91 cell = ''; 92 l2 = token.header.length; 93 for (j = 0; j < l2; j++) { 94 cell += this.renderer.tablecell( 95 this.parseInline(token.tokens.header[j]), 96 { header: true, align: token.align[j] } 97 ); 98 } 99 header += this.renderer.tablerow(cell); 100 101 body = ''; 102 l2 = token.cells.length; 103 for (j = 0; j < l2; j++) { 104 row = token.tokens.cells[j]; 105 106 cell = ''; 107 l3 = row.length; 108 for (k = 0; k < l3; k++) { 109 cell += this.renderer.tablecell( 110 this.parseInline(row[k]), 111 { header: false, align: token.align[k] } 112 ); 113 } 114 115 body += this.renderer.tablerow(cell); 116 } 117 out += this.renderer.table(header, body); 118 continue; 119 } 120 case 'blockquote': { 121 body = this.parse(token.tokens); 122 out += this.renderer.blockquote(body); 123 continue; 124 } 125 case 'list': { 126 ordered = token.ordered; 127 start = token.start; 128 loose = token.loose; 129 l2 = token.items.length; 130 131 body = ''; 132 for (j = 0; j < l2; j++) { 133 item = token.items[j]; 134 checked = item.checked; 135 task = item.task; 136 137 itemBody = ''; 138 if (item.task) { 139 checkbox = this.renderer.checkbox(checked); 140 if (loose) { 141 if (item.tokens.length > 0 && item.tokens[0].type === 'text') { 142 item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; 143 if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { 144 item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; 145 } 146 } else { 147 item.tokens.unshift({ 148 type: 'text', 149 text: checkbox 150 }); 151 } 152 } else { 153 itemBody += checkbox; 154 } 155 } 156 157 itemBody += this.parse(item.tokens, loose); 158 body += this.renderer.listitem(itemBody, task, checked); 159 } 160 161 out += this.renderer.list(body, ordered, start); 162 continue; 163 } 164 case 'html': { 165 // TODO parse inline content if parameter markdown=1 166 out += this.renderer.html(token.text); 167 continue; 168 } 169 case 'paragraph': { 170 out += this.renderer.paragraph(this.parseInline(token.tokens)); 171 continue; 172 } 173 case 'text': { 174 body = token.tokens ? this.parseInline(token.tokens) : token.text; 175 while (i + 1 < l && tokens[i + 1].type === 'text') { 176 token = tokens[++i]; 177 body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text); 178 } 179 out += top ? this.renderer.paragraph(body) : body; 180 continue; 181 } 182 default: { 183 const errMsg = 'Token with "' + token.type + '" type was not found.'; 184 if (this.options.silent) { 185 console.error(errMsg); 186 return; 187 } else { 188 throw new Error(errMsg); 189 } 190 } 191 } 192 } 193 194 return out; 195 } 196 197 /** 198 * Parse Inline Tokens 199 */ 200 parseInline(tokens, renderer) { 201 renderer = renderer || this.renderer; 202 let out = '', 203 i, 204 token; 205 206 const l = tokens.length; 207 for (i = 0; i < l; i++) { 208 token = tokens[i]; 209 switch (token.type) { 210 case 'escape': { 211 out += renderer.text(token.text); 212 break; 213 } 214 case 'html': { 215 out += renderer.html(token.text); 216 break; 217 } 218 case 'link': { 219 out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer)); 220 break; 221 } 222 case 'image': { 223 out += renderer.image(token.href, token.title, token.text); 224 break; 225 } 226 case 'strong': { 227 out += renderer.strong(this.parseInline(token.tokens, renderer)); 228 break; 229 } 230 case 'em': { 231 out += renderer.em(this.parseInline(token.tokens, renderer)); 232 break; 233 } 234 case 'codespan': { 235 out += renderer.codespan(token.text); 236 break; 237 } 238 case 'br': { 239 out += renderer.br(); 240 break; 241 } 242 case 'del': { 243 out += renderer.del(this.parseInline(token.tokens, renderer)); 244 break; 245 } 246 case 'text': { 247 out += renderer.text(token.text); 248 break; 249 } 250 default: { 251 const errMsg = 'Token with "' + token.type + '" type was not found.'; 252 if (this.options.silent) { 253 console.error(errMsg); 254 return; 255 } else { 256 throw new Error(errMsg); 257 } 258 } 259 } 260 } 261 return out; 262 } 263 };