Parser.js 6.8 KB

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