1 | const Renderer = require('./Renderer.js');
|
2 | const Slugger = require('./Slugger.js');
|
3 | const InlineLexer = require('./InlineLexer.js');
|
4 | const TextRenderer = require('./TextRenderer.js');
|
5 | const { defaults } = require('./defaults.js');
|
6 | const {
|
7 | merge,
|
8 | unescape
|
9 | } = require('./helpers.js');
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | module.exports = class Parser {
|
15 | constructor(options) {
|
16 | this.tokens = [];
|
17 | this.token = null;
|
18 | this.options = options || defaults;
|
19 | this.options.renderer = this.options.renderer || new Renderer();
|
20 | this.renderer = this.options.renderer;
|
21 | this.renderer.options = this.options;
|
22 | this.slugger = new Slugger();
|
23 | }
|
24 |
|
25 | |
26 |
|
27 |
|
28 | static parse(tokens, options) {
|
29 | const parser = new Parser(options);
|
30 | return parser.parse(tokens);
|
31 | };
|
32 |
|
33 | |
34 |
|
35 |
|
36 | parse(tokens) {
|
37 | this.inline = new InlineLexer(tokens.links, this.options);
|
38 |
|
39 | this.inlineText = new InlineLexer(
|
40 | tokens.links,
|
41 | merge({}, this.options, { renderer: new TextRenderer() })
|
42 | );
|
43 | this.tokens = tokens.reverse();
|
44 |
|
45 | let out = '';
|
46 | while (this.next()) {
|
47 | out += this.tok();
|
48 | }
|
49 |
|
50 | return out;
|
51 | };
|
52 |
|
53 | |
54 |
|
55 |
|
56 | next() {
|
57 | this.token = this.tokens.pop();
|
58 | return this.token;
|
59 | };
|
60 |
|
61 | |
62 |
|
63 |
|
64 | peek() {
|
65 | return this.tokens[this.tokens.length - 1] || 0;
|
66 | };
|
67 |
|
68 | |
69 |
|
70 |
|
71 | parseText() {
|
72 | let body = this.token.text;
|
73 |
|
74 | while (this.peek().type === 'text') {
|
75 | body += '\n' + this.next().text;
|
76 | }
|
77 |
|
78 | return this.inline.output(body);
|
79 | };
|
80 |
|
81 | |
82 |
|
83 |
|
84 | tok() {
|
85 | let body = '';
|
86 | switch (this.token.type) {
|
87 | case 'space': {
|
88 | return '';
|
89 | }
|
90 | case 'hr': {
|
91 | return this.renderer.hr();
|
92 | }
|
93 | case 'heading': {
|
94 | return this.renderer.heading(
|
95 | this.inline.output(this.token.text),
|
96 | this.token.depth,
|
97 | unescape(this.inlineText.output(this.token.text)),
|
98 | this.slugger);
|
99 | }
|
100 | case 'code': {
|
101 | return this.renderer.code(this.token.text,
|
102 | this.token.lang,
|
103 | this.token.escaped);
|
104 | }
|
105 | case 'table': {
|
106 | let header = '',
|
107 | i,
|
108 | row,
|
109 | cell,
|
110 | j;
|
111 |
|
112 |
|
113 | cell = '';
|
114 | for (i = 0; i < this.token.header.length; i++) {
|
115 | cell += this.renderer.tablecell(
|
116 | this.inline.output(this.token.header[i]),
|
117 | { header: true, align: this.token.align[i] }
|
118 | );
|
119 | }
|
120 | header += this.renderer.tablerow(cell);
|
121 |
|
122 | for (i = 0; i < this.token.cells.length; i++) {
|
123 | row = this.token.cells[i];
|
124 |
|
125 | cell = '';
|
126 | for (j = 0; j < row.length; j++) {
|
127 | cell += this.renderer.tablecell(
|
128 | this.inline.output(row[j]),
|
129 | { header: false, align: this.token.align[j] }
|
130 | );
|
131 | }
|
132 |
|
133 | body += this.renderer.tablerow(cell);
|
134 | }
|
135 | return this.renderer.table(header, body);
|
136 | }
|
137 | case 'blockquote_start': {
|
138 | body = '';
|
139 |
|
140 | while (this.next().type !== 'blockquote_end') {
|
141 | body += this.tok();
|
142 | }
|
143 |
|
144 | return this.renderer.blockquote(body);
|
145 | }
|
146 | case 'list_start': {
|
147 | body = '';
|
148 | const ordered = this.token.ordered,
|
149 | start = this.token.start;
|
150 |
|
151 | while (this.next().type !== 'list_end') {
|
152 | body += this.tok();
|
153 | }
|
154 |
|
155 | return this.renderer.list(body, ordered, start);
|
156 | }
|
157 | case 'list_item_start': {
|
158 | body = '';
|
159 | const loose = this.token.loose;
|
160 | const checked = this.token.checked;
|
161 | const task = this.token.task;
|
162 |
|
163 | if (this.token.task) {
|
164 | if (loose) {
|
165 | if (this.peek().type === 'text') {
|
166 | const nextToken = this.peek();
|
167 | nextToken.text = this.renderer.checkbox(checked) + ' ' + nextToken.text;
|
168 | } else {
|
169 | this.tokens.push({
|
170 | type: 'text',
|
171 | text: this.renderer.checkbox(checked)
|
172 | });
|
173 | }
|
174 | } else {
|
175 | body += this.renderer.checkbox(checked);
|
176 | }
|
177 | }
|
178 |
|
179 | while (this.next().type !== 'list_item_end') {
|
180 | body += !loose && this.token.type === 'text'
|
181 | ? this.parseText()
|
182 | : this.tok();
|
183 | }
|
184 | return this.renderer.listitem(body, task, checked);
|
185 | }
|
186 | case 'html': {
|
187 |
|
188 | return this.renderer.html(this.token.text);
|
189 | }
|
190 | case 'paragraph': {
|
191 | return this.renderer.paragraph(this.inline.output(this.token.text));
|
192 | }
|
193 | case 'text': {
|
194 | return this.renderer.paragraph(this.parseText());
|
195 | }
|
196 | default: {
|
197 | const errMsg = 'Token with "' + this.token.type + '" type was not found.';
|
198 | if (this.options.silent) {
|
199 | console.log(errMsg);
|
200 | } else {
|
201 | throw new Error(errMsg);
|
202 | }
|
203 | }
|
204 | }
|
205 | };
|
206 | };
|