Parser.js 6.6 KB

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