Parser.js 7.7 KB

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