UNPKG

13.2 kBJavaScriptView Raw
1const {
2 noopTest,
3 edit,
4 merge
5} = require('./helpers.js');
6
7/**
8 * Block-Level Grammar
9 */
10const block = {
11 newline: /^\n+/,
12 code: /^( {4}[^\n]+\n*)+/,
13 fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
14 hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
15 heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
16 blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
17 list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
18 html: '^ {0,3}(?:' // optional indentation
19 + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
20 + '|comment[^\\n]*(\\n+|$)' // (2)
21 + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
22 + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
23 + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
24 + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6)
25 + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag
26 + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag
27 + ')',
28 def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
29 nptable: noopTest,
30 table: noopTest,
31 lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,
32 // regex template, placeholders will be replaced according to different paragraph
33 // interruption rules of commonmark and the original markdown spec:
34 _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,
35 text: /^[^\n]+/
36};
37
38block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
39block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
40block.def = edit(block.def)
41 .replace('label', block._label)
42 .replace('title', block._title)
43 .getRegex();
44
45block.bullet = /(?:[*+-]|\d{1,9}[.)])/;
46block.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/;
47block.item = edit(block.item, 'gm')
48 .replace(/bull/g, block.bullet)
49 .getRegex();
50
51block.listItemStart = edit(/^( *)(bull)/)
52 .replace('bull', block.bullet)
53 .getRegex();
54
55block.list = edit(block.list)
56 .replace(/bull/g, block.bullet)
57 .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
58 .replace('def', '\\n+(?=' + block.def.source + ')')
59 .getRegex();
60
61block._tag = 'address|article|aside|base|basefont|blockquote|body|caption'
62 + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
63 + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
64 + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
65 + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr'
66 + '|track|ul';
67block._comment = /<!--(?!-?>)[\s\S]*?(?:-->|$)/;
68block.html = edit(block.html, 'i')
69 .replace('comment', block._comment)
70 .replace('tag', block._tag)
71 .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
72 .getRegex();
73
74block.paragraph = edit(block._paragraph)
75 .replace('hr', block.hr)
76 .replace('heading', ' {0,3}#{1,6} ')
77 .replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs
78 .replace('blockquote', ' {0,3}>')
79 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
80 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
81 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)')
82 .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
83 .getRegex();
84
85block.blockquote = edit(block.blockquote)
86 .replace('paragraph', block.paragraph)
87 .getRegex();
88
89/**
90 * Normal Block Grammar
91 */
92
93block.normal = merge({}, block);
94
95/**
96 * GFM Block Grammar
97 */
98
99block.gfm = merge({}, block.normal, {
100 nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header
101 + ' {0,3}([-:]+ *\\|[-| :]*)' // Align
102 + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)', // Cells
103 table: '^ *\\|(.+)\\n' // Header
104 + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align
105 + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells
106});
107
108block.gfm.nptable = edit(block.gfm.nptable)
109 .replace('hr', block.hr)
110 .replace('heading', ' {0,3}#{1,6} ')
111 .replace('blockquote', ' {0,3}>')
112 .replace('code', ' {4}[^\\n]')
113 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
114 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
115 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)')
116 .replace('tag', block._tag) // tables can be interrupted by type (6) html blocks
117 .getRegex();
118
119block.gfm.table = edit(block.gfm.table)
120 .replace('hr', block.hr)
121 .replace('heading', ' {0,3}#{1,6} ')
122 .replace('blockquote', ' {0,3}>')
123 .replace('code', ' {4}[^\\n]')
124 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
125 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
126 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)')
127 .replace('tag', block._tag) // tables can be interrupted by type (6) html blocks
128 .getRegex();
129
130/**
131 * Pedantic grammar (original John Gruber's loose markdown specification)
132 */
133
134block.pedantic = merge({}, block.normal, {
135 html: edit(
136 '^ *(?:comment *(?:\\n|\\s*$)'
137 + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
138 + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
139 .replace('comment', block._comment)
140 .replace(/tag/g, '(?!(?:'
141 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
142 + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
143 + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
144 .getRegex(),
145 def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
146 heading: /^(#{1,6})(.*)(?:\n+|$)/,
147 fences: noopTest, // fences not supported
148 paragraph: edit(block.normal._paragraph)
149 .replace('hr', block.hr)
150 .replace('heading', ' *#{1,6} *[^\n]')
151 .replace('lheading', block.lheading)
152 .replace('blockquote', ' {0,3}>')
153 .replace('|fences', '')
154 .replace('|list', '')
155 .replace('|html', '')
156 .getRegex()
157});
158
159/**
160 * Inline-Level Grammar
161 */
162const inline = {
163 escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
164 autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
165 url: noopTest,
166 tag: '^comment'
167 + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
168 + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
169 + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
170 + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
171 + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section
172 link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,
173 reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
174 nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
175 reflinkSearch: 'reflink|nolink(?!\\()',
176 strong: {
177 start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/, // (1) returns if starts w/ punctuation
178 middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,
179 endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline)
180 endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
181 },
182 em: {
183 start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/, // (1) returns if starts w/ punctuation
184 middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,
185 endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline)
186 endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
187 },
188 code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
189 br: /^( {2,}|\\)\n(?!\s*$)/,
190 del: noopTest,
191 text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*]|\b_|$)|[^ ](?= {2,}\n)))/,
192 punctuation: /^([\s*punctuation])/
193};
194
195// list of punctuation marks from common mark spec
196// without * and _ to workaround cases with double emphasis
197inline._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
198inline.punctuation = edit(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex();
199
200// sequences em should skip over [title](link), `code`, <html>
201inline._blockSkip = '\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>';
202inline._overlapSkip = '__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*';
203
204inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex();
205
206inline.em.start = edit(inline.em.start)
207 .replace(/punctuation/g, inline._punctuation)
208 .getRegex();
209
210inline.em.middle = edit(inline.em.middle)
211 .replace(/punctuation/g, inline._punctuation)
212 .replace(/overlapSkip/g, inline._overlapSkip)
213 .getRegex();
214
215inline.em.endAst = edit(inline.em.endAst, 'g')
216 .replace(/punctuation/g, inline._punctuation)
217 .getRegex();
218
219inline.em.endUnd = edit(inline.em.endUnd, 'g')
220 .replace(/punctuation/g, inline._punctuation)
221 .getRegex();
222
223inline.strong.start = edit(inline.strong.start)
224 .replace(/punctuation/g, inline._punctuation)
225 .getRegex();
226
227inline.strong.middle = edit(inline.strong.middle)
228 .replace(/punctuation/g, inline._punctuation)
229 .replace(/overlapSkip/g, inline._overlapSkip)
230 .getRegex();
231
232inline.strong.endAst = edit(inline.strong.endAst, 'g')
233 .replace(/punctuation/g, inline._punctuation)
234 .getRegex();
235
236inline.strong.endUnd = edit(inline.strong.endUnd, 'g')
237 .replace(/punctuation/g, inline._punctuation)
238 .getRegex();
239
240inline.blockSkip = edit(inline._blockSkip, 'g')
241 .getRegex();
242
243inline.overlapSkip = edit(inline._overlapSkip, 'g')
244 .getRegex();
245
246inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
247
248inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
249inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
250inline.autolink = edit(inline.autolink)
251 .replace('scheme', inline._scheme)
252 .replace('email', inline._email)
253 .getRegex();
254
255inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
256
257inline.tag = edit(inline.tag)
258 .replace('comment', inline._comment)
259 .replace('attribute', inline._attribute)
260 .getRegex();
261
262inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
263inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/;
264inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
265
266inline.link = edit(inline.link)
267 .replace('label', inline._label)
268 .replace('href', inline._href)
269 .replace('title', inline._title)
270 .getRegex();
271
272inline.reflink = edit(inline.reflink)
273 .replace('label', inline._label)
274 .getRegex();
275
276inline.reflinkSearch = edit(inline.reflinkSearch, 'g')
277 .replace('reflink', inline.reflink)
278 .replace('nolink', inline.nolink)
279 .getRegex();
280
281/**
282 * Normal Inline Grammar
283 */
284
285inline.normal = merge({}, inline);
286
287/**
288 * Pedantic Inline Grammar
289 */
290
291inline.pedantic = merge({}, inline.normal, {
292 strong: {
293 start: /^__|\*\*/,
294 middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
295 endAst: /\*\*(?!\*)/g,
296 endUnd: /__(?!_)/g
297 },
298 em: {
299 start: /^_|\*/,
300 middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
301 endAst: /\*(?!\*)/g,
302 endUnd: /_(?!_)/g
303 },
304 link: edit(/^!?\[(label)\]\((.*?)\)/)
305 .replace('label', inline._label)
306 .getRegex(),
307 reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
308 .replace('label', inline._label)
309 .getRegex()
310});
311
312/**
313 * GFM Inline Grammar
314 */
315
316inline.gfm = merge({}, inline.normal, {
317 escape: edit(inline.escape).replace('])', '~|])').getRegex(),
318 _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,
319 url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
320 _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
321 del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
322 text: /^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*~]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/
323});
324
325inline.gfm.url = edit(inline.gfm.url, 'i')
326 .replace('email', inline.gfm._extended_email)
327 .getRegex();
328/**
329 * GFM + Line Breaks Inline Grammar
330 */
331
332inline.breaks = merge({}, inline.gfm, {
333 br: edit(inline.br).replace('{2,}', '*').getRegex(),
334 text: edit(inline.gfm.text)
335 .replace('\\b_', '\\b_| {2,}\\n')
336 .replace(/\{2,\}/g, '*')
337 .getRegex()
338});
339
340module.exports = {
341 block,
342 inline
343};