1 | const Renderer = require('./Renderer.js');
|
2 | const { defaults } = require('./defaults.js');
|
3 | const { inline } = require('./rules.js');
|
4 | const {
|
5 | findClosingBracket,
|
6 | escape
|
7 | } = require('./helpers.js');
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | module.exports = class InlineLexer {
|
13 | constructor(links, options) {
|
14 | this.options = options || defaults;
|
15 | this.links = links;
|
16 | this.rules = inline.normal;
|
17 | this.options.renderer = this.options.renderer || new Renderer();
|
18 | this.renderer = this.options.renderer;
|
19 | this.renderer.options = this.options;
|
20 |
|
21 | if (!this.links) {
|
22 | throw new Error('Tokens array requires a `links` property.');
|
23 | }
|
24 |
|
25 | if (this.options.pedantic) {
|
26 | this.rules = inline.pedantic;
|
27 | } else if (this.options.gfm) {
|
28 | if (this.options.breaks) {
|
29 | this.rules = inline.breaks;
|
30 | } else {
|
31 | this.rules = inline.gfm;
|
32 | }
|
33 | }
|
34 | }
|
35 |
|
36 | |
37 |
|
38 |
|
39 | static get rules() {
|
40 | return inline;
|
41 | }
|
42 |
|
43 | |
44 |
|
45 |
|
46 | static output(src, links, options) {
|
47 | const inline = new InlineLexer(links, options);
|
48 | return inline.output(src);
|
49 | }
|
50 |
|
51 | |
52 |
|
53 |
|
54 | output(src) {
|
55 | let out = '',
|
56 | link,
|
57 | text,
|
58 | href,
|
59 | title,
|
60 | cap,
|
61 | prevCapZero;
|
62 |
|
63 | while (src) {
|
64 |
|
65 | if (cap = this.rules.escape.exec(src)) {
|
66 | src = src.substring(cap[0].length);
|
67 | out += escape(cap[1]);
|
68 | continue;
|
69 | }
|
70 |
|
71 |
|
72 | if (cap = this.rules.tag.exec(src)) {
|
73 | if (!this.inLink && /^<a /i.test(cap[0])) {
|
74 | this.inLink = true;
|
75 | } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
|
76 | this.inLink = false;
|
77 | }
|
78 | if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
79 | this.inRawBlock = true;
|
80 | } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
81 | this.inRawBlock = false;
|
82 | }
|
83 |
|
84 | src = src.substring(cap[0].length);
|
85 | out += this.options.sanitize
|
86 | ? this.options.sanitizer
|
87 | ? this.options.sanitizer(cap[0])
|
88 | : escape(cap[0])
|
89 | : cap[0];
|
90 | continue;
|
91 | }
|
92 |
|
93 |
|
94 | if (cap = this.rules.link.exec(src)) {
|
95 | const lastParenIndex = findClosingBracket(cap[2], '()');
|
96 | if (lastParenIndex > -1) {
|
97 | const start = cap[0].indexOf('!') === 0 ? 5 : 4;
|
98 | const linkLen = start + cap[1].length + lastParenIndex;
|
99 | cap[2] = cap[2].substring(0, lastParenIndex);
|
100 | cap[0] = cap[0].substring(0, linkLen).trim();
|
101 | cap[3] = '';
|
102 | }
|
103 | src = src.substring(cap[0].length);
|
104 | this.inLink = true;
|
105 | href = cap[2];
|
106 | if (this.options.pedantic) {
|
107 | link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
108 |
|
109 | if (link) {
|
110 | href = link[1];
|
111 | title = link[3];
|
112 | } else {
|
113 | title = '';
|
114 | }
|
115 | } else {
|
116 | title = cap[3] ? cap[3].slice(1, -1) : '';
|
117 | }
|
118 | href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
|
119 | out += this.outputLink(cap, {
|
120 | href: InlineLexer.escapes(href),
|
121 | title: InlineLexer.escapes(title)
|
122 | });
|
123 | this.inLink = false;
|
124 | continue;
|
125 | }
|
126 |
|
127 |
|
128 | if ((cap = this.rules.reflink.exec(src))
|
129 | || (cap = this.rules.nolink.exec(src))) {
|
130 | src = src.substring(cap[0].length);
|
131 | link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
132 | link = this.links[link.toLowerCase()];
|
133 | if (!link || !link.href) {
|
134 | out += cap[0].charAt(0);
|
135 | src = cap[0].substring(1) + src;
|
136 | continue;
|
137 | }
|
138 | this.inLink = true;
|
139 | out += this.outputLink(cap, link);
|
140 | this.inLink = false;
|
141 | continue;
|
142 | }
|
143 |
|
144 |
|
145 | if (cap = this.rules.strong.exec(src)) {
|
146 | src = src.substring(cap[0].length);
|
147 | out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1]));
|
148 | continue;
|
149 | }
|
150 |
|
151 |
|
152 | if (cap = this.rules.em.exec(src)) {
|
153 | src = src.substring(cap[0].length);
|
154 | out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1]));
|
155 | continue;
|
156 | }
|
157 |
|
158 |
|
159 | if (cap = this.rules.code.exec(src)) {
|
160 | src = src.substring(cap[0].length);
|
161 | out += this.renderer.codespan(escape(cap[2].trim(), true));
|
162 | continue;
|
163 | }
|
164 |
|
165 |
|
166 | if (cap = this.rules.br.exec(src)) {
|
167 | src = src.substring(cap[0].length);
|
168 | out += this.renderer.br();
|
169 | continue;
|
170 | }
|
171 |
|
172 |
|
173 | if (cap = this.rules.del.exec(src)) {
|
174 | src = src.substring(cap[0].length);
|
175 | out += this.renderer.del(this.output(cap[1]));
|
176 | continue;
|
177 | }
|
178 |
|
179 |
|
180 | if (cap = this.rules.autolink.exec(src)) {
|
181 | src = src.substring(cap[0].length);
|
182 | if (cap[2] === '@') {
|
183 | text = escape(this.mangle(cap[1]));
|
184 | href = 'mailto:' + text;
|
185 | } else {
|
186 | text = escape(cap[1]);
|
187 | href = text;
|
188 | }
|
189 | out += this.renderer.link(href, null, text);
|
190 | continue;
|
191 | }
|
192 |
|
193 |
|
194 | if (!this.inLink && (cap = this.rules.url.exec(src))) {
|
195 | if (cap[2] === '@') {
|
196 | text = escape(cap[0]);
|
197 | href = 'mailto:' + text;
|
198 | } else {
|
199 |
|
200 | do {
|
201 | prevCapZero = cap[0];
|
202 | cap[0] = this.rules._backpedal.exec(cap[0])[0];
|
203 | } while (prevCapZero !== cap[0]);
|
204 | text = escape(cap[0]);
|
205 | if (cap[1] === 'www.') {
|
206 | href = 'http://' + text;
|
207 | } else {
|
208 | href = text;
|
209 | }
|
210 | }
|
211 | src = src.substring(cap[0].length);
|
212 | out += this.renderer.link(href, null, text);
|
213 | continue;
|
214 | }
|
215 |
|
216 |
|
217 | if (cap = this.rules.text.exec(src)) {
|
218 | src = src.substring(cap[0].length);
|
219 | if (this.inRawBlock) {
|
220 | out += this.renderer.text(this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]);
|
221 | } else {
|
222 | out += this.renderer.text(escape(this.smartypants(cap[0])));
|
223 | }
|
224 | continue;
|
225 | }
|
226 |
|
227 | if (src) {
|
228 | throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
229 | }
|
230 | }
|
231 |
|
232 | return out;
|
233 | }
|
234 |
|
235 | static escapes(text) {
|
236 | return text ? text.replace(InlineLexer.rules._escapes, '$1') : text;
|
237 | }
|
238 |
|
239 | |
240 |
|
241 |
|
242 | outputLink(cap, link) {
|
243 | const href = link.href,
|
244 | title = link.title ? escape(link.title) : null;
|
245 |
|
246 | return cap[0].charAt(0) !== '!'
|
247 | ? this.renderer.link(href, title, this.output(cap[1]))
|
248 | : this.renderer.image(href, title, escape(cap[1]));
|
249 | }
|
250 |
|
251 | |
252 |
|
253 |
|
254 | smartypants(text) {
|
255 | if (!this.options.smartypants) return text;
|
256 | return text
|
257 |
|
258 | .replace(/---/g, '\u2014')
|
259 |
|
260 | .replace(/--/g, '\u2013')
|
261 |
|
262 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
|
263 |
|
264 | .replace(/'/g, '\u2019')
|
265 |
|
266 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
|
267 |
|
268 | .replace(/"/g, '\u201d')
|
269 |
|
270 | .replace(/\.{3}/g, '\u2026');
|
271 | }
|
272 |
|
273 | |
274 |
|
275 |
|
276 | mangle(text) {
|
277 | if (!this.options.mangle) return text;
|
278 | const l = text.length;
|
279 | let out = '',
|
280 | i = 0,
|
281 | ch;
|
282 |
|
283 | for (; i < l; i++) {
|
284 | ch = text.charCodeAt(i);
|
285 | if (Math.random() > 0.5) {
|
286 | ch = 'x' + ch.toString(16);
|
287 | }
|
288 | out += '&#' + ch + ';';
|
289 | }
|
290 |
|
291 | return out;
|
292 | }
|
293 | };
|