Merge branch 'master' into master
This commit is contained in:
commit
c735fa58ef
@ -17,6 +17,7 @@
|
||||
"one-var": "off",
|
||||
"no-control-regex": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-extra-semi": "error",
|
||||
|
||||
"prefer-const": "error",
|
||||
"no-var": "error"
|
||||
|
@ -4,7 +4,7 @@ To champion the single-responsibility and open/closed principles, we have tried
|
||||
|
||||
<h2 id="renderer">The renderer</h2>
|
||||
|
||||
The renderer is...
|
||||
The renderer defines the output of the parser.
|
||||
|
||||
**Example:** Overriding default heading token by adding an embedded anchor tag like on GitHub.
|
||||
|
||||
@ -29,7 +29,7 @@ renderer.heading = function (text, level) {
|
||||
};
|
||||
|
||||
// Run marked
|
||||
console.log(marked('# heading+', { renderer: renderer }));
|
||||
console.log(marked('# heading+', { renderer }));
|
||||
```
|
||||
|
||||
**Output:**
|
||||
@ -91,12 +91,11 @@ slugger.slug('foo-1') // foo-1-2
|
||||
|
||||
<h2 id="lexer">The lexer</h2>
|
||||
|
||||
The lexer is...
|
||||
|
||||
The lexer turns a markdown string into tokens.
|
||||
|
||||
<h2 id="parser">The parser</h2>
|
||||
|
||||
The parser is...
|
||||
The parser takes tokens as input and calls the renderer functions.
|
||||
|
||||
***
|
||||
|
||||
@ -105,30 +104,46 @@ The parser is...
|
||||
You also have direct access to the lexer and parser if you so desire.
|
||||
|
||||
``` js
|
||||
const tokens = marked.lexer(text, options);
|
||||
const tokens = marked.lexer(markdown, options);
|
||||
console.log(marked.parser(tokens, options));
|
||||
```
|
||||
|
||||
``` js
|
||||
const lexer = new marked.Lexer(options);
|
||||
const tokens = lexer.lex(text);
|
||||
const tokens = lexer.lex(markdown);
|
||||
console.log(tokens);
|
||||
console.log(lexer.rules);
|
||||
console.log(lexer.rules.block); // block level rules
|
||||
console.log(lexer.rules.inline); // inline level rules
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ node
|
||||
> require('marked').lexer('> i am using marked.')
|
||||
[ { type: 'blockquote_start' },
|
||||
{ type: 'paragraph',
|
||||
text: 'i am using marked.' },
|
||||
{ type: 'blockquote_end' },
|
||||
links: {} ]
|
||||
> require('marked').lexer('> I am using marked.')
|
||||
[
|
||||
{
|
||||
type: "blockquote",
|
||||
raw: "> I am using marked.",
|
||||
tokens: [
|
||||
{
|
||||
type: "paragraph",
|
||||
raw: "I am using marked.",
|
||||
text: "I am using marked.",
|
||||
tokens: [
|
||||
{
|
||||
type: "text",
|
||||
raw: "I am using marked.",
|
||||
text: "I am using marked."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
links: {}
|
||||
]
|
||||
```
|
||||
|
||||
The Lexers build an array of tokens, which will be passed to their respective
|
||||
Parsers. The Parsers process each token in the token arrays,
|
||||
which are removed from the array of tokens:
|
||||
The Lexer builds an array of tokens, which will be passed to the Parser.
|
||||
The Parser processes each token in the token array:
|
||||
|
||||
``` js
|
||||
const marked = require('marked');
|
||||
@ -146,18 +161,60 @@ console.log(tokens);
|
||||
|
||||
const html = marked.parser(tokens);
|
||||
console.log(html);
|
||||
|
||||
console.log(tokens);
|
||||
```
|
||||
|
||||
``` bash
|
||||
[ { type: 'heading', depth: 1, text: 'heading' },
|
||||
{ type: 'paragraph', text: ' [link][1]' },
|
||||
{ type: 'space' },
|
||||
links: { '1': { href: '#heading', title: 'heading' } } ]
|
||||
|
||||
[
|
||||
{
|
||||
type: "heading",
|
||||
raw: " # heading\n\n",
|
||||
depth: 1,
|
||||
text: "heading",
|
||||
tokens: [
|
||||
{
|
||||
type: "text",
|
||||
raw: "heading",
|
||||
text: "heading"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "paragraph",
|
||||
raw: " [link][1]",
|
||||
text: " [link][1]",
|
||||
tokens: [
|
||||
{
|
||||
type: "text",
|
||||
raw: " ",
|
||||
text: " "
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
raw: "[link][1]",
|
||||
text: "link",
|
||||
href: "#heading",
|
||||
title: "heading",
|
||||
tokens: [
|
||||
{
|
||||
type: "text",
|
||||
raw: "link",
|
||||
text: "link"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "space",
|
||||
raw: "\n\n"
|
||||
},
|
||||
links: {
|
||||
"1": {
|
||||
href: "#heading",
|
||||
title: "heading"
|
||||
}
|
||||
}
|
||||
]
|
||||
<h1 id="heading">heading</h1>
|
||||
<p> <a href="#heading" title="heading">link</a></p>
|
||||
|
||||
[ links: { '1': { href: '#heading', title: 'heading' } } ]
|
||||
```
|
||||
|
@ -183,7 +183,7 @@ function handleIframeLoad() {
|
||||
|
||||
function handleInput() {
|
||||
inputDirty = true;
|
||||
};
|
||||
}
|
||||
|
||||
function handleVersionChange() {
|
||||
if ($markedVerElem.value === 'commit' || $markedVerElem.value === 'pr') {
|
||||
@ -256,7 +256,7 @@ function handleChange(panes, visiblePane) {
|
||||
}
|
||||
}
|
||||
return active;
|
||||
};
|
||||
}
|
||||
|
||||
function addCommitVersion(value, text, commit) {
|
||||
if (markedVersions[value]) {
|
||||
@ -331,13 +331,13 @@ function jsonString(input) {
|
||||
.replace(/[\\"']/g, '\\$&')
|
||||
.replace(/\u0000/g, '\\0');
|
||||
return '"' + output + '"';
|
||||
};
|
||||
}
|
||||
|
||||
function getScrollSize() {
|
||||
var e = $activeOutputElem;
|
||||
|
||||
return e.scrollHeight - e.clientHeight;
|
||||
};
|
||||
}
|
||||
|
||||
function getScrollPercent() {
|
||||
var size = getScrollSize();
|
||||
@ -347,11 +347,11 @@ function getScrollPercent() {
|
||||
}
|
||||
|
||||
return $activeOutputElem.scrollTop / size;
|
||||
};
|
||||
}
|
||||
|
||||
function setScrollPercent(percent) {
|
||||
$activeOutputElem.scrollTop = percent * getScrollSize();
|
||||
};
|
||||
}
|
||||
|
||||
function updateLink() {
|
||||
var outputType = '';
|
||||
@ -446,7 +446,7 @@ function checkForChanges() {
|
||||
}
|
||||
}
|
||||
checkChangeTimeout = window.setTimeout(checkForChanges, delayTime);
|
||||
};
|
||||
}
|
||||
|
||||
function setResponseTime(ms) {
|
||||
var amount = ms;
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare
|
||||
|
||||
if (!self.Promise) {
|
||||
self.importScripts('https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.js');
|
||||
self.Promise = ES6Promise;
|
||||
@ -48,28 +49,44 @@ function parse(e) {
|
||||
case 'parse':
|
||||
var startTime = new Date();
|
||||
var lexed = marked.lexer(e.data.markdown, e.data.options);
|
||||
var lexedList = [];
|
||||
for (var i = 0; i < lexed.length; i++) {
|
||||
var lexedLine = [];
|
||||
for (var j in lexed[i]) {
|
||||
lexedLine.push(j + ':' + jsonString(lexed[i][j]));
|
||||
}
|
||||
lexedList.push('{' + lexedLine.join(', ') + '}');
|
||||
}
|
||||
var lexedList = getLexedList(lexed);
|
||||
var parsed = marked.parser(lexed, e.data.options);
|
||||
var endTime = new Date();
|
||||
// setTimeout(function () {
|
||||
postMessage({
|
||||
task: e.data.task,
|
||||
lexed: lexedList.join('\n'),
|
||||
lexed: lexedList,
|
||||
parsed: parsed,
|
||||
time: endTime - startTime
|
||||
});
|
||||
// }, 10000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getLexedList(lexed, level) {
|
||||
level = level || 0;
|
||||
var lexedList = [];
|
||||
for (var i = 0; i < lexed.length; i++) {
|
||||
var lexedLine = [];
|
||||
for (var j in lexed[i]) {
|
||||
if (j === 'tokens' || j === 'items') {
|
||||
lexedLine.push(j + ': [\n' + getLexedList(lexed[i][j], level + 1) + '\n]');
|
||||
} else {
|
||||
lexedLine.push(j + ':' + jsonString(lexed[i][j]));
|
||||
}
|
||||
}
|
||||
lexedList.push(stringRepeat(' ', 2 * level) + '{' + lexedLine.join(', ') + '}');
|
||||
}
|
||||
return lexedList.join('\n');
|
||||
}
|
||||
|
||||
function stringRepeat(char, times) {
|
||||
var s = '';
|
||||
for (var i = 0; i < times; i++) {
|
||||
s += char;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function jsonString(input) {
|
||||
var output = (input + '')
|
||||
.replace(/\n/g, '\\n')
|
||||
@ -79,7 +96,7 @@ function jsonString(input) {
|
||||
.replace(/[\\"']/g, '\\$&')
|
||||
.replace(/\u0000/g, '\\0');
|
||||
return '"' + output + '"';
|
||||
};
|
||||
}
|
||||
|
||||
function loadVersion(ver) {
|
||||
var promise;
|
||||
|
1436
lib/marked.esm.js
1436
lib/marked.esm.js
File diff suppressed because it is too large
Load Diff
1533
lib/marked.js
1533
lib/marked.js
File diff suppressed because it is too large
Load Diff
2
marked.min.js
vendored
2
marked.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,293 +0,0 @@
|
||||
const Renderer = require('./Renderer.js');
|
||||
const { defaults } = require('./defaults.js');
|
||||
const { inline } = require('./rules.js');
|
||||
const {
|
||||
findClosingBracket,
|
||||
escape
|
||||
} = require('./helpers.js');
|
||||
|
||||
/**
|
||||
* Inline Lexer & Compiler
|
||||
*/
|
||||
module.exports = class InlineLexer {
|
||||
constructor(links, options) {
|
||||
this.options = options || defaults;
|
||||
this.links = links;
|
||||
this.rules = inline.normal;
|
||||
this.options.renderer = this.options.renderer || new Renderer();
|
||||
this.renderer = this.options.renderer;
|
||||
this.renderer.options = this.options;
|
||||
|
||||
if (!this.links) {
|
||||
throw new Error('Tokens array requires a `links` property.');
|
||||
}
|
||||
|
||||
if (this.options.pedantic) {
|
||||
this.rules = inline.pedantic;
|
||||
} else if (this.options.gfm) {
|
||||
if (this.options.breaks) {
|
||||
this.rules = inline.breaks;
|
||||
} else {
|
||||
this.rules = inline.gfm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose Inline Rules
|
||||
*/
|
||||
static get rules() {
|
||||
return inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static Lexing/Compiling Method
|
||||
*/
|
||||
static output(src, links, options) {
|
||||
const inline = new InlineLexer(links, options);
|
||||
return inline.output(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexing/Compiling
|
||||
*/
|
||||
output(src) {
|
||||
let out = '',
|
||||
link,
|
||||
text,
|
||||
href,
|
||||
title,
|
||||
cap,
|
||||
prevCapZero;
|
||||
|
||||
while (src) {
|
||||
// escape
|
||||
if (cap = this.rules.escape.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += escape(cap[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// tag
|
||||
if (cap = this.rules.tag.exec(src)) {
|
||||
if (!this.inLink && /^<a /i.test(cap[0])) {
|
||||
this.inLink = true;
|
||||
} else if (this.inLink && /^<\/a>/i.test(cap[0])) {
|
||||
this.inLink = false;
|
||||
}
|
||||
if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
||||
this.inRawBlock = true;
|
||||
} else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
||||
this.inRawBlock = false;
|
||||
}
|
||||
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.html(this.options.sanitize
|
||||
? (this.options.sanitizer
|
||||
? this.options.sanitizer(cap[0])
|
||||
: escape(cap[0]))
|
||||
: cap[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// link
|
||||
if (cap = this.rules.link.exec(src)) {
|
||||
const lastParenIndex = findClosingBracket(cap[2], '()');
|
||||
if (lastParenIndex > -1) {
|
||||
const start = cap[0].indexOf('!') === 0 ? 5 : 4;
|
||||
const linkLen = start + cap[1].length + lastParenIndex;
|
||||
cap[2] = cap[2].substring(0, lastParenIndex);
|
||||
cap[0] = cap[0].substring(0, linkLen).trim();
|
||||
cap[3] = '';
|
||||
}
|
||||
src = src.substring(cap[0].length);
|
||||
this.inLink = true;
|
||||
href = cap[2];
|
||||
if (this.options.pedantic) {
|
||||
link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
||||
|
||||
if (link) {
|
||||
href = link[1];
|
||||
title = link[3];
|
||||
} else {
|
||||
title = '';
|
||||
}
|
||||
} else {
|
||||
title = cap[3] ? cap[3].slice(1, -1) : '';
|
||||
}
|
||||
href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
|
||||
out += this.outputLink(cap, {
|
||||
href: InlineLexer.escapes(href),
|
||||
title: InlineLexer.escapes(title)
|
||||
});
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// reflink, nolink
|
||||
if ((cap = this.rules.reflink.exec(src))
|
||||
|| (cap = this.rules.nolink.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
||||
link = this.links[link.toLowerCase()];
|
||||
if (!link || !link.href) {
|
||||
out += cap[0].charAt(0);
|
||||
src = cap[0].substring(1) + src;
|
||||
continue;
|
||||
}
|
||||
this.inLink = true;
|
||||
out += this.outputLink(cap, link);
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// strong
|
||||
if (cap = this.rules.strong.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// em
|
||||
if (cap = this.rules.em.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// code
|
||||
if (cap = this.rules.code.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.codespan(escape(cap[2].trim(), true));
|
||||
continue;
|
||||
}
|
||||
|
||||
// br
|
||||
if (cap = this.rules.br.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.br();
|
||||
continue;
|
||||
}
|
||||
|
||||
// del (gfm)
|
||||
if (cap = this.rules.del.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.del(this.output(cap[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// autolink
|
||||
if (cap = this.rules.autolink.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
if (cap[2] === '@') {
|
||||
text = escape(this.mangle(cap[1]));
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
text = escape(cap[1]);
|
||||
href = text;
|
||||
}
|
||||
out += this.renderer.link(href, null, text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// url (gfm)
|
||||
if (!this.inLink && (cap = this.rules.url.exec(src))) {
|
||||
if (cap[2] === '@') {
|
||||
text = escape(cap[0]);
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
// do extended autolink path validation
|
||||
do {
|
||||
prevCapZero = cap[0];
|
||||
cap[0] = this.rules._backpedal.exec(cap[0])[0];
|
||||
} while (prevCapZero !== cap[0]);
|
||||
text = escape(cap[0]);
|
||||
if (cap[1] === 'www.') {
|
||||
href = 'http://' + text;
|
||||
} else {
|
||||
href = text;
|
||||
}
|
||||
}
|
||||
src = src.substring(cap[0].length);
|
||||
out += this.renderer.link(href, null, text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// text
|
||||
if (cap = this.rules.text.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
if (this.inRawBlock) {
|
||||
out += this.renderer.text(this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]);
|
||||
} else {
|
||||
out += this.renderer.text(escape(this.smartypants(cap[0])));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src) {
|
||||
throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static escapes(text) {
|
||||
return text ? text.replace(InlineLexer.rules._escapes, '$1') : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Link
|
||||
*/
|
||||
outputLink(cap, link) {
|
||||
const href = link.href,
|
||||
title = link.title ? escape(link.title) : null;
|
||||
|
||||
return cap[0].charAt(0) !== '!'
|
||||
? this.renderer.link(href, title, this.output(cap[1]))
|
||||
: this.renderer.image(href, title, escape(cap[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smartypants Transformations
|
||||
*/
|
||||
smartypants(text) {
|
||||
if (!this.options.smartypants) return text;
|
||||
return text
|
||||
// em-dashes
|
||||
.replace(/---/g, '\u2014')
|
||||
// en-dashes
|
||||
.replace(/--/g, '\u2013')
|
||||
// opening singles
|
||||
.replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
|
||||
// closing singles & apostrophes
|
||||
.replace(/'/g, '\u2019')
|
||||
// opening doubles
|
||||
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
|
||||
// closing doubles
|
||||
.replace(/"/g, '\u201d')
|
||||
// ellipses
|
||||
.replace(/\.{3}/g, '\u2026');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mangle Links
|
||||
*/
|
||||
mangle(text) {
|
||||
if (!this.options.mangle) return text;
|
||||
const l = text.length;
|
||||
let out = '',
|
||||
i = 0,
|
||||
ch;
|
||||
|
||||
for (; i < l; i++) {
|
||||
ch = text.charCodeAt(i);
|
||||
if (Math.random() > 0.5) {
|
||||
ch = 'x' + ch.toString(16);
|
||||
}
|
||||
out += '&#' + ch + ';';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
653
src/Lexer.js
653
src/Lexer.js
@ -1,9 +1,10 @@
|
||||
const { defaults } = require('./defaults.js');
|
||||
const { block } = require('./rules.js');
|
||||
const { block, inline } = require('./rules.js');
|
||||
const {
|
||||
rtrim,
|
||||
splitCells,
|
||||
escape
|
||||
escape,
|
||||
findClosingBracket
|
||||
} = require('./helpers.js');
|
||||
|
||||
/**
|
||||
@ -14,12 +15,21 @@ module.exports = class Lexer {
|
||||
this.tokens = [];
|
||||
this.tokens.links = Object.create(null);
|
||||
this.options = options || defaults;
|
||||
this.rules = block.normal;
|
||||
this.rules = {
|
||||
block: block.normal,
|
||||
inline: inline.normal
|
||||
};
|
||||
|
||||
if (this.options.pedantic) {
|
||||
this.rules = block.pedantic;
|
||||
this.rules.block = block.pedantic;
|
||||
this.rules.inline = inline.pedantic;
|
||||
} else if (this.options.gfm) {
|
||||
this.rules = block.gfm;
|
||||
this.rules.block = block.gfm;
|
||||
if (this.options.breaks) {
|
||||
this.rules.inline = inline.breaks;
|
||||
} else {
|
||||
this.rules.inline = inline.gfm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +37,10 @@ module.exports = class Lexer {
|
||||
* Expose Block Rules
|
||||
*/
|
||||
static get rules() {
|
||||
return block;
|
||||
return {
|
||||
block,
|
||||
inline
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,7 +49,7 @@ module.exports = class Lexer {
|
||||
static lex(src, options) {
|
||||
const lexer = new Lexer(options);
|
||||
return lexer.lex(src);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocessing
|
||||
@ -46,13 +59,17 @@ module.exports = class Lexer {
|
||||
.replace(/\r\n|\r/g, '\n')
|
||||
.replace(/\t/g, ' ');
|
||||
|
||||
return this.token(src, true);
|
||||
};
|
||||
this.blockTokens(src, this.tokens);
|
||||
|
||||
this.inline(this.tokens);
|
||||
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexing
|
||||
*/
|
||||
token(src, top) {
|
||||
blockTokens(src, tokens, top = true) {
|
||||
src = src.replace(/^ +$/gm, '');
|
||||
let next,
|
||||
loose,
|
||||
@ -60,39 +77,45 @@ module.exports = class Lexer {
|
||||
bull,
|
||||
b,
|
||||
item,
|
||||
listStart,
|
||||
listItems,
|
||||
t,
|
||||
list,
|
||||
space,
|
||||
i,
|
||||
tag,
|
||||
l,
|
||||
isordered,
|
||||
istask,
|
||||
ischecked;
|
||||
ischecked,
|
||||
lastToken,
|
||||
addBack,
|
||||
raw;
|
||||
|
||||
while (src) {
|
||||
// newline
|
||||
if (cap = this.rules.newline.exec(src)) {
|
||||
if (cap = this.rules.block.newline.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
if (cap[0].length > 1) {
|
||||
this.tokens.push({
|
||||
type: 'space'
|
||||
tokens.push({
|
||||
type: 'space',
|
||||
raw
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// code
|
||||
if (cap = this.rules.code.exec(src)) {
|
||||
const lastToken = this.tokens[this.tokens.length - 1];
|
||||
if (cap = this.rules.block.code.exec(src)) {
|
||||
lastToken = tokens[tokens.length - 1];
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
// An indented code block cannot interrupt a paragraph.
|
||||
if (lastToken && lastToken.type === 'paragraph') {
|
||||
lastToken.text += '\n' + cap[0].trimRight();
|
||||
lastToken.raw += '\n' + raw;
|
||||
} else {
|
||||
cap = cap[0].replace(/^ {4}/gm, '');
|
||||
this.tokens.push({
|
||||
tokens.push({
|
||||
type: 'code',
|
||||
raw,
|
||||
codeBlockStyle: 'indented',
|
||||
text: !this.options.pedantic
|
||||
? rtrim(cap, '\n')
|
||||
@ -103,10 +126,12 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// fences
|
||||
if (cap = this.rules.fences.exec(src)) {
|
||||
if (cap = this.rules.block.fences.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'code',
|
||||
raw,
|
||||
lang: cap[2] ? cap[2].trim() : cap[2],
|
||||
text: cap[3] || ''
|
||||
});
|
||||
@ -114,10 +139,12 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// heading
|
||||
if (cap = this.rules.heading.exec(src)) {
|
||||
if (cap = this.rules.block.heading.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'heading',
|
||||
raw,
|
||||
depth: cap[1].length,
|
||||
text: cap[2]
|
||||
});
|
||||
@ -125,7 +152,7 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// table no leading pipe (gfm)
|
||||
if (cap = this.rules.nptable.exec(src)) {
|
||||
if (cap = this.rules.block.nptable.exec(src)) {
|
||||
item = {
|
||||
type: 'table',
|
||||
header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
|
||||
@ -135,8 +162,11 @@ module.exports = class Lexer {
|
||||
|
||||
if (item.header.length === item.align.length) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
item.raw = raw;
|
||||
|
||||
for (i = 0; i < item.align.length; i++) {
|
||||
l = item.align.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
if (/^ *-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = 'right';
|
||||
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
||||
@ -148,72 +178,71 @@ module.exports = class Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < item.cells.length; i++) {
|
||||
l = item.cells.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
item.cells[i] = splitCells(item.cells[i], item.header.length);
|
||||
}
|
||||
|
||||
this.tokens.push(item);
|
||||
tokens.push(item);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// hr
|
||||
if (cap = this.rules.hr.exec(src)) {
|
||||
if (cap = this.rules.block.hr.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
type: 'hr'
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'hr',
|
||||
raw
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// blockquote
|
||||
if (cap = this.rules.blockquote.exec(src)) {
|
||||
if (cap = this.rules.block.blockquote.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
|
||||
this.tokens.push({
|
||||
type: 'blockquote_start'
|
||||
});
|
||||
raw = cap[0];
|
||||
|
||||
cap = cap[0].replace(/^ *> ?/gm, '');
|
||||
|
||||
// Pass `top` to keep the current
|
||||
// "toplevel" state. This is exactly
|
||||
// how markdown.pl works.
|
||||
this.token(cap, top);
|
||||
|
||||
this.tokens.push({
|
||||
type: 'blockquote_end'
|
||||
tokens.push({
|
||||
type: 'blockquote',
|
||||
raw,
|
||||
tokens: this.blockTokens(cap, [], top)
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// list
|
||||
if (cap = this.rules.list.exec(src)) {
|
||||
if (cap = this.rules.block.list.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
bull = cap[2];
|
||||
isordered = bull.length > 1;
|
||||
|
||||
listStart = {
|
||||
type: 'list_start',
|
||||
list = {
|
||||
type: 'list',
|
||||
raw,
|
||||
ordered: isordered,
|
||||
start: isordered ? +bull : '',
|
||||
loose: false
|
||||
loose: false,
|
||||
items: []
|
||||
};
|
||||
|
||||
this.tokens.push(listStart);
|
||||
tokens.push(list);
|
||||
|
||||
// Get each top-level item.
|
||||
cap = cap[0].match(this.rules.item);
|
||||
cap = cap[0].match(this.rules.block.item);
|
||||
|
||||
listItems = [];
|
||||
next = false;
|
||||
l = cap.length;
|
||||
i = 0;
|
||||
|
||||
for (; i < l; i++) {
|
||||
l = cap.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
item = cap[i];
|
||||
raw = item.trim();
|
||||
|
||||
// Remove the list item's bullet
|
||||
// so it is seen as the next token.
|
||||
@ -235,7 +264,9 @@ module.exports = class Lexer {
|
||||
b = block.bullet.exec(cap[i + 1])[0];
|
||||
if (bull.length > 1 ? b.length === 1
|
||||
: (b.length > 1 || (this.options.smartLists && b !== bull))) {
|
||||
src = cap.slice(i + 1).join('\n') + src;
|
||||
addBack = cap.slice(i + 1).join('\n');
|
||||
src = addBack + src;
|
||||
list.raw = list.raw.substring(list.raw.length - addBack.length);
|
||||
i = l - 1;
|
||||
}
|
||||
}
|
||||
@ -250,7 +281,7 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
if (loose) {
|
||||
listStart.loose = true;
|
||||
list.loose = true;
|
||||
}
|
||||
|
||||
// Check for task list items
|
||||
@ -261,46 +292,27 @@ module.exports = class Lexer {
|
||||
item = item.replace(/^\[[ xX]\] +/, '');
|
||||
}
|
||||
|
||||
t = {
|
||||
type: 'list_item_start',
|
||||
list.items.push({
|
||||
raw,
|
||||
task: istask,
|
||||
checked: ischecked,
|
||||
loose: loose
|
||||
};
|
||||
|
||||
listItems.push(t);
|
||||
this.tokens.push(t);
|
||||
|
||||
// Recurse.
|
||||
this.token(item, false);
|
||||
|
||||
this.tokens.push({
|
||||
type: 'list_item_end'
|
||||
loose: loose,
|
||||
tokens: this.blockTokens(item, [], false)
|
||||
});
|
||||
}
|
||||
|
||||
if (listStart.loose) {
|
||||
l = listItems.length;
|
||||
i = 0;
|
||||
for (; i < l; i++) {
|
||||
listItems[i].loose = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.tokens.push({
|
||||
type: 'list_end'
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// html
|
||||
if (cap = this.rules.html.exec(src)) {
|
||||
if (cap = this.rules.block.html.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: this.options.sanitize
|
||||
? 'paragraph'
|
||||
: 'html',
|
||||
raw,
|
||||
pre: !this.options.sanitizer
|
||||
&& (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
|
||||
text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
|
||||
@ -309,7 +321,7 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// def
|
||||
if (top && (cap = this.rules.def.exec(src))) {
|
||||
if (top && (cap = this.rules.block.def.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
|
||||
tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
|
||||
@ -323,7 +335,7 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// table (gfm)
|
||||
if (cap = this.rules.table.exec(src)) {
|
||||
if (cap = this.rules.block.table.exec(src)) {
|
||||
item = {
|
||||
type: 'table',
|
||||
header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
|
||||
@ -333,8 +345,10 @@ module.exports = class Lexer {
|
||||
|
||||
if (item.header.length === item.align.length) {
|
||||
src = src.substring(cap[0].length);
|
||||
item.raw = cap[0];
|
||||
|
||||
for (i = 0; i < item.align.length; i++) {
|
||||
l = item.align.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
if (/^ *-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = 'right';
|
||||
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
||||
@ -346,23 +360,26 @@ module.exports = class Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < item.cells.length; i++) {
|
||||
l = item.cells.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
item.cells[i] = splitCells(
|
||||
item.cells[i].replace(/^ *\| *| *\| *$/g, ''),
|
||||
item.header.length);
|
||||
}
|
||||
|
||||
this.tokens.push(item);
|
||||
tokens.push(item);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// lheading
|
||||
if (cap = this.rules.lheading.exec(src)) {
|
||||
if (cap = this.rules.block.lheading.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'heading',
|
||||
raw,
|
||||
depth: cap[2].charAt(0) === '=' ? 1 : 2,
|
||||
text: cap[1]
|
||||
});
|
||||
@ -370,10 +387,12 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// top-level paragraph
|
||||
if (top && (cap = this.rules.paragraph.exec(src))) {
|
||||
if (top && (cap = this.rules.block.paragraph.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'paragraph',
|
||||
raw,
|
||||
text: cap[1].charAt(cap[1].length - 1) === '\n'
|
||||
? cap[1].slice(0, -1)
|
||||
: cap[1]
|
||||
@ -382,21 +401,463 @@ module.exports = class Lexer {
|
||||
}
|
||||
|
||||
// text
|
||||
if (cap = this.rules.text.exec(src)) {
|
||||
if (cap = this.rules.block.text.exec(src)) {
|
||||
// Top-level should never reach here.
|
||||
src = src.substring(cap[0].length);
|
||||
this.tokens.push({
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
raw,
|
||||
text: cap[0]
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src) {
|
||||
throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
||||
const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
|
||||
if (this.options.silent) {
|
||||
console.error(errMsg);
|
||||
} else {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.tokens;
|
||||
};
|
||||
return tokens;
|
||||
}
|
||||
|
||||
inline(tokens) {
|
||||
let i,
|
||||
j,
|
||||
k,
|
||||
l2,
|
||||
row,
|
||||
token;
|
||||
|
||||
const l = tokens.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
token = tokens[i];
|
||||
switch (token.type) {
|
||||
case 'paragraph':
|
||||
case 'text':
|
||||
case 'heading': {
|
||||
token.tokens = [];
|
||||
this.inlineTokens(token.text, token.tokens);
|
||||
break;
|
||||
}
|
||||
case 'table': {
|
||||
token.tokens = {
|
||||
header: [],
|
||||
cells: []
|
||||
};
|
||||
|
||||
// header
|
||||
l2 = token.header.length;
|
||||
for (j = 0; j < l2; j++) {
|
||||
token.tokens.header[j] = [];
|
||||
this.inlineTokens(token.header[j], token.tokens.header[j]);
|
||||
}
|
||||
|
||||
// cells
|
||||
l2 = token.cells.length;
|
||||
for (j = 0; j < l2; j++) {
|
||||
row = token.cells[j];
|
||||
token.tokens.cells[j] = [];
|
||||
for (k = 0; k < row.length; k++) {
|
||||
token.tokens.cells[j][k] = [];
|
||||
this.inlineTokens(row[k], token.tokens.cells[j][k]);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'blockquote': {
|
||||
this.inline(token.tokens);
|
||||
break;
|
||||
}
|
||||
case 'list': {
|
||||
l2 = token.items.length;
|
||||
for (j = 0; j < l2; j++) {
|
||||
this.inline(token.items[j].tokens);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexing/Compiling
|
||||
*/
|
||||
inlineTokens(src, tokens) {
|
||||
let out = '',
|
||||
link,
|
||||
text,
|
||||
newTokens,
|
||||
href,
|
||||
title,
|
||||
cap,
|
||||
prevCapZero,
|
||||
lastParenIndex,
|
||||
start,
|
||||
linkLen,
|
||||
raw;
|
||||
|
||||
while (src) {
|
||||
// escape
|
||||
if (cap = this.rules.inline.escape.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
text = escape(cap[1]);
|
||||
out += text;
|
||||
tokens.push({
|
||||
type: 'escape',
|
||||
raw,
|
||||
text
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// tag
|
||||
if (cap = this.rules.inline.tag.exec(src)) {
|
||||
if (!this.inLink && /^<a /i.test(cap[0])) {
|
||||
this.inLink = true;
|
||||
} else if (this.inLink && /^<\/a>/i.test(cap[0])) {
|
||||
this.inLink = false;
|
||||
}
|
||||
if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
||||
this.inRawBlock = true;
|
||||
} else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
||||
this.inRawBlock = false;
|
||||
}
|
||||
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
text = this.options.sanitize
|
||||
? (this.options.sanitizer
|
||||
? this.options.sanitizer(cap[0])
|
||||
: escape(cap[0]))
|
||||
: cap[0];
|
||||
tokens.push({
|
||||
type: this.options.sanitize
|
||||
? 'text'
|
||||
: 'html',
|
||||
raw,
|
||||
text
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// link
|
||||
if (cap = this.rules.inline.link.exec(src)) {
|
||||
lastParenIndex = findClosingBracket(cap[2], '()');
|
||||
if (lastParenIndex > -1) {
|
||||
start = cap[0].indexOf('!') === 0 ? 5 : 4;
|
||||
linkLen = start + cap[1].length + lastParenIndex;
|
||||
cap[2] = cap[2].substring(0, lastParenIndex);
|
||||
cap[0] = cap[0].substring(0, linkLen).trim();
|
||||
cap[3] = '';
|
||||
}
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
this.inLink = true;
|
||||
href = cap[2];
|
||||
if (this.options.pedantic) {
|
||||
link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
||||
|
||||
if (link) {
|
||||
href = link[1];
|
||||
title = link[3];
|
||||
} else {
|
||||
title = '';
|
||||
}
|
||||
} else {
|
||||
title = cap[3] ? cap[3].slice(1, -1) : '';
|
||||
}
|
||||
href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
|
||||
out += this.outputLink(cap, {
|
||||
href: this.escapes(href),
|
||||
title: this.escapes(title)
|
||||
}, tokens, raw);
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// reflink, nolink
|
||||
if ((cap = this.rules.inline.reflink.exec(src))
|
||||
|| (cap = this.rules.inline.nolink.exec(src))) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
||||
link = this.tokens.links[link.toLowerCase()];
|
||||
if (!link || !link.href) {
|
||||
text = cap[0].charAt(0);
|
||||
out += text;
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
raw: text,
|
||||
text
|
||||
});
|
||||
src = cap[0].substring(1) + src;
|
||||
continue;
|
||||
}
|
||||
this.inLink = true;
|
||||
out += this.outputLink(cap, link, tokens, raw);
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// strong
|
||||
if (cap = this.rules.inline.strong.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
newTokens = tokens ? [] : null;
|
||||
text = this.inlineTokens(cap[4] || cap[3] || cap[2] || cap[1], newTokens);
|
||||
|
||||
tokens.push({
|
||||
type: 'strong',
|
||||
raw,
|
||||
text,
|
||||
tokens: newTokens
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// em
|
||||
if (cap = this.rules.inline.em.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
newTokens = tokens ? [] : null;
|
||||
text = this.inlineTokens(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1], newTokens);
|
||||
tokens.push({
|
||||
type: 'em',
|
||||
raw,
|
||||
text,
|
||||
tokens: newTokens
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// code
|
||||
if (cap = this.rules.inline.code.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
text = escape(cap[2].trim(), true);
|
||||
tokens.push({
|
||||
type: 'codespan',
|
||||
raw,
|
||||
text
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// br
|
||||
if (cap = this.rules.inline.br.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'br',
|
||||
raw
|
||||
});
|
||||
out += '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// del (gfm)
|
||||
if (cap = this.rules.inline.del.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
newTokens = tokens ? [] : null;
|
||||
text = this.inlineTokens(cap[1], newTokens);
|
||||
tokens.push({
|
||||
type: 'del',
|
||||
raw,
|
||||
text,
|
||||
tokens: newTokens
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// autolink
|
||||
if (cap = this.rules.inline.autolink.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
if (cap[2] === '@') {
|
||||
text = escape(this.options.mangle ? this.mangle(cap[1]) : cap[1]);
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
text = escape(cap[1]);
|
||||
href = text;
|
||||
}
|
||||
tokens.push({
|
||||
type: 'link',
|
||||
raw,
|
||||
text,
|
||||
href,
|
||||
tokens: [
|
||||
{
|
||||
type: 'text',
|
||||
raw: text,
|
||||
text
|
||||
}
|
||||
]
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// url (gfm)
|
||||
if (!this.inLink && (cap = this.rules.inline.url.exec(src))) {
|
||||
if (cap[2] === '@') {
|
||||
text = escape(this.options.mangle ? this.mangle(cap[0]) : cap[0]);
|
||||
href = 'mailto:' + text;
|
||||
} else {
|
||||
// do extended autolink path validation
|
||||
do {
|
||||
prevCapZero = cap[0];
|
||||
cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
|
||||
} while (prevCapZero !== cap[0]);
|
||||
text = escape(cap[0]);
|
||||
if (cap[1] === 'www.') {
|
||||
href = 'http://' + text;
|
||||
} else {
|
||||
href = text;
|
||||
}
|
||||
}
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
tokens.push({
|
||||
type: 'link',
|
||||
raw,
|
||||
text,
|
||||
href,
|
||||
tokens: [
|
||||
{
|
||||
type: 'text',
|
||||
raw: text,
|
||||
text
|
||||
}
|
||||
]
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// text
|
||||
if (cap = this.rules.inline.text.exec(src)) {
|
||||
src = src.substring(cap[0].length);
|
||||
raw = cap[0];
|
||||
if (this.inRawBlock) {
|
||||
text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
|
||||
} else {
|
||||
text = escape(this.options.smartypants ? this.smartypants(cap[0]) : cap[0]);
|
||||
}
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
raw,
|
||||
text
|
||||
});
|
||||
out += text;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src) {
|
||||
const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
|
||||
if (this.options.silent) {
|
||||
console.error(errMsg);
|
||||
} else {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
escapes(text) {
|
||||
return text ? text.replace(inline._escapes, '$1') : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* tokenize Link
|
||||
*/
|
||||
outputLink(cap, link, tokens, raw) {
|
||||
const href = link.href;
|
||||
const title = link.title ? escape(link.title) : null;
|
||||
const newTokens = tokens ? [] : null;
|
||||
|
||||
if (cap[0].charAt(0) !== '!') {
|
||||
const text = this.inlineTokens(cap[1], newTokens);
|
||||
tokens.push({
|
||||
type: 'link',
|
||||
raw,
|
||||
text,
|
||||
href,
|
||||
title,
|
||||
tokens: newTokens
|
||||
});
|
||||
return text;
|
||||
} else {
|
||||
const text = escape(cap[1]);
|
||||
tokens.push({
|
||||
type: 'image',
|
||||
raw,
|
||||
text,
|
||||
href,
|
||||
title
|
||||
});
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Smartypants Transformations
|
||||
*/
|
||||
smartypants(text) {
|
||||
return text
|
||||
// em-dashes
|
||||
.replace(/---/g, '\u2014')
|
||||
// en-dashes
|
||||
.replace(/--/g, '\u2013')
|
||||
// opening singles
|
||||
.replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
|
||||
// closing singles & apostrophes
|
||||
.replace(/'/g, '\u2019')
|
||||
// opening doubles
|
||||
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
|
||||
// closing doubles
|
||||
.replace(/"/g, '\u201d')
|
||||
// ellipses
|
||||
.replace(/\.{3}/g, '\u2026');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mangle Links
|
||||
*/
|
||||
mangle(text) {
|
||||
let out = '',
|
||||
i,
|
||||
ch;
|
||||
|
||||
const l = text.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
ch = text.charCodeAt(i);
|
||||
if (Math.random() > 0.5) {
|
||||
ch = 'x' + ch.toString(16);
|
||||
}
|
||||
out += '&#' + ch + ';';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
367
src/Parser.js
367
src/Parser.js
@ -1,10 +1,8 @@
|
||||
const Renderer = require('./Renderer.js');
|
||||
const Slugger = require('./Slugger.js');
|
||||
const InlineLexer = require('./InlineLexer.js');
|
||||
const TextRenderer = require('./TextRenderer.js');
|
||||
const Slugger = require('./Slugger.js');
|
||||
const { defaults } = require('./defaults.js');
|
||||
const {
|
||||
merge,
|
||||
unescape
|
||||
} = require('./helpers.js');
|
||||
|
||||
@ -13,12 +11,11 @@ const {
|
||||
*/
|
||||
module.exports = class Parser {
|
||||
constructor(options) {
|
||||
this.tokens = [];
|
||||
this.token = null;
|
||||
this.options = options || defaults;
|
||||
this.options.renderer = this.options.renderer || new Renderer();
|
||||
this.renderer = this.options.renderer;
|
||||
this.renderer.options = this.options;
|
||||
this.textRenderer = new TextRenderer();
|
||||
this.slugger = new Slugger();
|
||||
}
|
||||
|
||||
@ -28,179 +25,231 @@ module.exports = class Parser {
|
||||
static parse(tokens, options) {
|
||||
const parser = new Parser(options);
|
||||
return parser.parse(tokens);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Loop
|
||||
*/
|
||||
parse(tokens) {
|
||||
this.inline = new InlineLexer(tokens.links, this.options);
|
||||
// use an InlineLexer with a TextRenderer to extract pure text
|
||||
this.inlineText = new InlineLexer(
|
||||
tokens.links,
|
||||
merge({}, this.options, { renderer: new TextRenderer() })
|
||||
);
|
||||
this.tokens = tokens.reverse();
|
||||
parse(tokens, top = true) {
|
||||
let out = '',
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l2,
|
||||
l3,
|
||||
row,
|
||||
cell,
|
||||
header,
|
||||
body,
|
||||
token,
|
||||
ordered,
|
||||
start,
|
||||
loose,
|
||||
itemBody,
|
||||
item,
|
||||
checked,
|
||||
task,
|
||||
checkbox;
|
||||
|
||||
let out = '';
|
||||
while (this.next()) {
|
||||
out += this.tok();
|
||||
const l = tokens.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
token = tokens[i];
|
||||
switch (token.type) {
|
||||
case 'space': {
|
||||
continue;
|
||||
}
|
||||
case 'hr': {
|
||||
out += this.renderer.hr();
|
||||
continue;
|
||||
}
|
||||
case 'heading': {
|
||||
out += this.renderer.heading(
|
||||
this.parseInline(token.tokens),
|
||||
token.depth,
|
||||
unescape(this.parseInline(token.tokens, this.textRenderer)),
|
||||
this.slugger);
|
||||
continue;
|
||||
}
|
||||
case 'code': {
|
||||
out += this.renderer.code(token.text,
|
||||
token.lang,
|
||||
token.escaped);
|
||||
continue;
|
||||
}
|
||||
case 'table': {
|
||||
header = '';
|
||||
|
||||
// header
|
||||
cell = '';
|
||||
l2 = token.header.length;
|
||||
for (j = 0; j < l2; j++) {
|
||||
cell += this.renderer.tablecell(
|
||||
this.parseInline(token.tokens.header[j]),
|
||||
{ header: true, align: token.align[j] }
|
||||
);
|
||||
}
|
||||
header += this.renderer.tablerow(cell);
|
||||
|
||||
body = '';
|
||||
l2 = token.cells.length;
|
||||
for (j = 0; j < l2; j++) {
|
||||
row = token.tokens.cells[j];
|
||||
|
||||
cell = '';
|
||||
l3 = row.length;
|
||||
for (k = 0; k < l3; k++) {
|
||||
cell += this.renderer.tablecell(
|
||||
this.parseInline(row[k]),
|
||||
{ header: false, align: token.align[k] }
|
||||
);
|
||||
}
|
||||
|
||||
body += this.renderer.tablerow(cell);
|
||||
}
|
||||
out += this.renderer.table(header, body);
|
||||
continue;
|
||||
}
|
||||
case 'blockquote': {
|
||||
body = this.parse(token.tokens);
|
||||
out += this.renderer.blockquote(body);
|
||||
continue;
|
||||
}
|
||||
case 'list': {
|
||||
ordered = token.ordered;
|
||||
start = token.start;
|
||||
loose = token.loose;
|
||||
l2 = token.items.length;
|
||||
|
||||
body = '';
|
||||
for (j = 0; j < l2; j++) {
|
||||
item = token.items[j];
|
||||
checked = item.checked;
|
||||
task = item.task;
|
||||
|
||||
itemBody = '';
|
||||
if (item.task) {
|
||||
checkbox = this.renderer.checkbox(checked);
|
||||
if (loose) {
|
||||
if (item.tokens[0].type === 'text') {
|
||||
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
|
||||
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
|
||||
item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
|
||||
}
|
||||
} else {
|
||||
item.tokens.unshift({
|
||||
type: 'text',
|
||||
text: checkbox
|
||||
});
|
||||
}
|
||||
} else {
|
||||
itemBody += checkbox;
|
||||
}
|
||||
}
|
||||
|
||||
itemBody += this.parse(item.tokens, loose);
|
||||
body += this.renderer.listitem(itemBody, task, checked);
|
||||
}
|
||||
|
||||
out += this.renderer.list(body, ordered, start);
|
||||
continue;
|
||||
}
|
||||
case 'html': {
|
||||
// TODO parse inline content if parameter markdown=1
|
||||
out += this.renderer.html(token.text);
|
||||
continue;
|
||||
}
|
||||
case 'paragraph': {
|
||||
out += this.renderer.paragraph(this.parseInline(token.tokens));
|
||||
continue;
|
||||
}
|
||||
case 'text': {
|
||||
body = token.tokens ? this.parseInline(token.tokens) : token.text;
|
||||
while (i + 1 < l && tokens[i + 1].type === 'text') {
|
||||
token = tokens[++i];
|
||||
body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
|
||||
}
|
||||
out += top ? this.renderer.paragraph(body) : body;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
||||
if (this.options.silent) {
|
||||
console.error(errMsg);
|
||||
return;
|
||||
} else {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Next Token
|
||||
* Parse Inline Tokens
|
||||
*/
|
||||
next() {
|
||||
this.token = this.tokens.pop();
|
||||
return this.token;
|
||||
};
|
||||
parseInline(tokens, renderer) {
|
||||
renderer = renderer || this.renderer;
|
||||
let out = '',
|
||||
i,
|
||||
token;
|
||||
|
||||
/**
|
||||
* Preview Next Token
|
||||
*/
|
||||
peek() {
|
||||
return this.tokens[this.tokens.length - 1] || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse Text Tokens
|
||||
*/
|
||||
parseText() {
|
||||
let body = this.token.text;
|
||||
|
||||
while (this.peek().type === 'text') {
|
||||
body += '\n' + this.next().text;
|
||||
}
|
||||
|
||||
return this.inline.output(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse Current Token
|
||||
*/
|
||||
tok() {
|
||||
let body = '';
|
||||
switch (this.token.type) {
|
||||
case 'space': {
|
||||
return '';
|
||||
}
|
||||
case 'hr': {
|
||||
return this.renderer.hr();
|
||||
}
|
||||
case 'heading': {
|
||||
return this.renderer.heading(
|
||||
this.inline.output(this.token.text),
|
||||
this.token.depth,
|
||||
unescape(this.inlineText.output(this.token.text)),
|
||||
this.slugger);
|
||||
}
|
||||
case 'code': {
|
||||
return this.renderer.code(this.token.text,
|
||||
this.token.lang,
|
||||
this.token.escaped);
|
||||
}
|
||||
case 'table': {
|
||||
let header = '',
|
||||
i,
|
||||
row,
|
||||
cell,
|
||||
j;
|
||||
|
||||
// header
|
||||
cell = '';
|
||||
for (i = 0; i < this.token.header.length; i++) {
|
||||
cell += this.renderer.tablecell(
|
||||
this.inline.output(this.token.header[i]),
|
||||
{ header: true, align: this.token.align[i] }
|
||||
);
|
||||
const l = tokens.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
token = tokens[i];
|
||||
switch (token.type) {
|
||||
case 'escape': {
|
||||
out += token.text;
|
||||
break;
|
||||
}
|
||||
header += this.renderer.tablerow(cell);
|
||||
|
||||
for (i = 0; i < this.token.cells.length; i++) {
|
||||
row = this.token.cells[i];
|
||||
|
||||
cell = '';
|
||||
for (j = 0; j < row.length; j++) {
|
||||
cell += this.renderer.tablecell(
|
||||
this.inline.output(row[j]),
|
||||
{ header: false, align: this.token.align[j] }
|
||||
);
|
||||
}
|
||||
|
||||
body += this.renderer.tablerow(cell);
|
||||
case 'html': {
|
||||
out += renderer.html(token.text);
|
||||
break;
|
||||
}
|
||||
return this.renderer.table(header, body);
|
||||
}
|
||||
case 'blockquote_start': {
|
||||
body = '';
|
||||
|
||||
while (this.next().type !== 'blockquote_end') {
|
||||
body += this.tok();
|
||||
case 'link': {
|
||||
out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
|
||||
break;
|
||||
}
|
||||
|
||||
return this.renderer.blockquote(body);
|
||||
}
|
||||
case 'list_start': {
|
||||
body = '';
|
||||
const ordered = this.token.ordered,
|
||||
start = this.token.start;
|
||||
|
||||
while (this.next().type !== 'list_end') {
|
||||
body += this.tok();
|
||||
case 'image': {
|
||||
out += renderer.image(token.href, token.title, token.text);
|
||||
break;
|
||||
}
|
||||
|
||||
return this.renderer.list(body, ordered, start);
|
||||
}
|
||||
case 'list_item_start': {
|
||||
body = '';
|
||||
const loose = this.token.loose;
|
||||
const checked = this.token.checked;
|
||||
const task = this.token.task;
|
||||
|
||||
if (this.token.task) {
|
||||
if (loose) {
|
||||
if (this.peek().type === 'text') {
|
||||
const nextToken = this.peek();
|
||||
nextToken.text = this.renderer.checkbox(checked) + ' ' + nextToken.text;
|
||||
} else {
|
||||
this.tokens.push({
|
||||
type: 'text',
|
||||
text: this.renderer.checkbox(checked)
|
||||
});
|
||||
}
|
||||
case 'strong': {
|
||||
out += renderer.strong(this.parseInline(token.tokens, renderer));
|
||||
break;
|
||||
}
|
||||
case 'em': {
|
||||
out += renderer.em(this.parseInline(token.tokens, renderer));
|
||||
break;
|
||||
}
|
||||
case 'codespan': {
|
||||
out += renderer.codespan(token.text);
|
||||
break;
|
||||
}
|
||||
case 'br': {
|
||||
out += renderer.br();
|
||||
break;
|
||||
}
|
||||
case 'del': {
|
||||
out += renderer.del(this.parseInline(token.tokens, renderer));
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
out += renderer.text(token.text);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
||||
if (this.options.silent) {
|
||||
console.error(errMsg);
|
||||
return;
|
||||
} else {
|
||||
body += this.renderer.checkbox(checked);
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
while (this.next().type !== 'list_item_end') {
|
||||
body += !loose && this.token.type === 'text'
|
||||
? this.parseText()
|
||||
: this.tok();
|
||||
}
|
||||
return this.renderer.listitem(body, task, checked);
|
||||
}
|
||||
case 'html': {
|
||||
// TODO parse inline content if parameter markdown=1
|
||||
return this.renderer.html(this.token.text);
|
||||
}
|
||||
case 'paragraph': {
|
||||
return this.renderer.paragraph(this.inline.output(this.token.text));
|
||||
}
|
||||
case 'text': {
|
||||
return this.renderer.paragraph(this.parseText());
|
||||
}
|
||||
default: {
|
||||
const errMsg = 'Token with "' + this.token.type + '" type was not found.';
|
||||
if (this.options.silent) {
|
||||
console.log(errMsg);
|
||||
} else {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
@ -34,15 +34,15 @@ module.exports = class Renderer {
|
||||
+ '">'
|
||||
+ (escaped ? code : escape(code, true))
|
||||
+ '</code></pre>\n';
|
||||
};
|
||||
}
|
||||
|
||||
blockquote(quote) {
|
||||
return '<blockquote>\n' + quote + '</blockquote>\n';
|
||||
};
|
||||
}
|
||||
|
||||
html(html) {
|
||||
return html;
|
||||
};
|
||||
}
|
||||
|
||||
heading(text, level, raw, slugger) {
|
||||
if (this.options.headerIds) {
|
||||
@ -59,21 +59,21 @@ module.exports = class Renderer {
|
||||
}
|
||||
// ignore IDs
|
||||
return '<h' + level + '>' + text + '</h' + level + '>\n';
|
||||
};
|
||||
}
|
||||
|
||||
hr() {
|
||||
return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
|
||||
};
|
||||
}
|
||||
|
||||
list(body, ordered, start) {
|
||||
const type = ordered ? 'ol' : 'ul',
|
||||
startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
|
||||
return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
|
||||
};
|
||||
}
|
||||
|
||||
listitem(text) {
|
||||
return '<li>' + text + '</li>\n';
|
||||
};
|
||||
}
|
||||
|
||||
checkbox(checked) {
|
||||
return '<input '
|
||||
@ -81,11 +81,11 @@ module.exports = class Renderer {
|
||||
+ 'disabled="" type="checkbox"'
|
||||
+ (this.options.xhtml ? ' /' : '')
|
||||
+ '> ';
|
||||
};
|
||||
}
|
||||
|
||||
paragraph(text) {
|
||||
return '<p>' + text + '</p>\n';
|
||||
};
|
||||
}
|
||||
|
||||
table(header, body) {
|
||||
if (body) body = '<tbody>' + body + '</tbody>';
|
||||
@ -96,11 +96,11 @@ module.exports = class Renderer {
|
||||
+ '</thead>\n'
|
||||
+ body
|
||||
+ '</table>\n';
|
||||
};
|
||||
}
|
||||
|
||||
tablerow(content) {
|
||||
return '<tr>\n' + content + '</tr>\n';
|
||||
};
|
||||
}
|
||||
|
||||
tablecell(content, flags) {
|
||||
const type = flags.header ? 'th' : 'td';
|
||||
@ -108,28 +108,28 @@ module.exports = class Renderer {
|
||||
? '<' + type + ' align="' + flags.align + '">'
|
||||
: '<' + type + '>';
|
||||
return tag + content + '</' + type + '>\n';
|
||||
};
|
||||
}
|
||||
|
||||
// span level renderer
|
||||
strong(text) {
|
||||
return '<strong>' + text + '</strong>';
|
||||
};
|
||||
}
|
||||
|
||||
em(text) {
|
||||
return '<em>' + text + '</em>';
|
||||
};
|
||||
}
|
||||
|
||||
codespan(text) {
|
||||
return '<code>' + text + '</code>';
|
||||
};
|
||||
}
|
||||
|
||||
br() {
|
||||
return this.options.xhtml ? '<br/>' : '<br>';
|
||||
};
|
||||
}
|
||||
|
||||
del(text) {
|
||||
return '<del>' + text + '</del>';
|
||||
};
|
||||
}
|
||||
|
||||
link(href, title, text) {
|
||||
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
||||
@ -142,7 +142,7 @@ module.exports = class Renderer {
|
||||
}
|
||||
out += '>' + text + '</a>';
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
image(href, title, text) {
|
||||
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
||||
@ -156,9 +156,9 @@ module.exports = class Renderer {
|
||||
}
|
||||
out += this.options.xhtml ? '/>' : '>';
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
text(text) {
|
||||
return text;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -29,5 +29,5 @@ module.exports = class Slugger {
|
||||
this.seen[slug] = 0;
|
||||
|
||||
return slug;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,6 @@ const Lexer = require('./Lexer.js');
|
||||
const Parser = require('./Parser.js');
|
||||
const Renderer = require('./Renderer.js');
|
||||
const TextRenderer = require('./TextRenderer.js');
|
||||
const InlineLexer = require('./InlineLexer.js');
|
||||
const Slugger = require('./Slugger.js');
|
||||
const {
|
||||
merge,
|
||||
@ -140,9 +139,6 @@ marked.TextRenderer = TextRenderer;
|
||||
marked.Lexer = Lexer;
|
||||
marked.lexer = Lexer.lex;
|
||||
|
||||
marked.InlineLexer = InlineLexer;
|
||||
marked.inlineLexer = InlineLexer.output;
|
||||
|
||||
marked.Slugger = Slugger;
|
||||
|
||||
marked.parse = marked;
|
||||
|
@ -21,6 +21,22 @@ beforeEach(() => {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
},
|
||||
toEqualHtml: () => {
|
||||
return {
|
||||
compare: async(actual, expected) => {
|
||||
const result = {};
|
||||
result.pass = await htmlDiffer.isEqual(expected, actual);
|
||||
|
||||
if (result.pass) {
|
||||
result.message = `Expected '${actual}' not to equal '${expected}'`;
|
||||
} else {
|
||||
const diff = await htmlDiffer.firstDiff(actual, expected);
|
||||
result.message = `Expected: ${diff.expected}\n Actual: ${diff.actual}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
1019
test/unit/Lexer-spec.js
Normal file
1019
test/unit/Lexer-spec.js
Normal file
File diff suppressed because it is too large
Load Diff
449
test/unit/Parser-spec.js
Normal file
449
test/unit/Parser-spec.js
Normal file
@ -0,0 +1,449 @@
|
||||
const Parser = require('../../src/Parser.js');
|
||||
|
||||
async function expectHtml({ tokens, options, html, inline }) {
|
||||
const parser = new Parser(options);
|
||||
const actual = parser[inline ? 'parseInline' : 'parse'](tokens);
|
||||
await expectAsync(actual).toEqualHtml(html);
|
||||
}
|
||||
|
||||
describe('Parser', () => {
|
||||
describe('block', () => {
|
||||
it('space between paragraphs', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'paragraph 1',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'paragraph 1' }
|
||||
]
|
||||
},
|
||||
{ type: 'space' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'paragraph 2',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'paragraph 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
html: '<p>paragraph 1</p><p>paragraph 2</p>'
|
||||
});
|
||||
});
|
||||
|
||||
it('hr', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'hr'
|
||||
}],
|
||||
html: '<hr />'
|
||||
});
|
||||
});
|
||||
|
||||
it('heading', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'heading',
|
||||
depth: 1,
|
||||
text: 'heading',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'heading' }
|
||||
]
|
||||
}],
|
||||
html: '<h1 id="heading">heading</h1>'
|
||||
});
|
||||
});
|
||||
|
||||
it('code', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'code',
|
||||
text: 'code'
|
||||
}],
|
||||
html: '<pre><code>code</code></pre>'
|
||||
});
|
||||
});
|
||||
|
||||
it('table', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'table',
|
||||
header: ['a', 'b'],
|
||||
align: ['left', 'right'],
|
||||
cells: [['1', '2']],
|
||||
tokens: {
|
||||
header: [
|
||||
[{ type: 'text', text: 'a' }],
|
||||
[{ type: 'text', text: 'b' }]
|
||||
],
|
||||
cells: [
|
||||
[
|
||||
[{ type: 'text', text: '1' }],
|
||||
[{ type: 'text', text: '2' }]
|
||||
]
|
||||
]
|
||||
}
|
||||
}],
|
||||
html: `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">a</th>
|
||||
<th align="right">b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left">1</td>
|
||||
<td align="right">2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>`
|
||||
});
|
||||
});
|
||||
|
||||
it('blockquote', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'blockquote',
|
||||
tokens: [{
|
||||
type: 'paragraph',
|
||||
text: 'blockquote',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'blockquote' }
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
html: '<blockquote><p>blockquote</p></blockquote>'
|
||||
});
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('unordered', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'list',
|
||||
ordered: false,
|
||||
start: '',
|
||||
loose: false,
|
||||
items: [
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 1',
|
||||
tokens: [{ type: 'text', text: 'item 1' }]
|
||||
}]
|
||||
},
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 2',
|
||||
tokens: [{ type: 'text', text: 'item 2' }]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
html: `
|
||||
<ul>
|
||||
<li>item 1</li>
|
||||
<li>item 2</li>
|
||||
</ul>`
|
||||
});
|
||||
});
|
||||
|
||||
it('ordered', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'list',
|
||||
ordered: true,
|
||||
start: 2,
|
||||
loose: false,
|
||||
items: [
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 1',
|
||||
tokens: [{ type: 'text', text: 'item 1' }]
|
||||
}]
|
||||
},
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 2',
|
||||
tokens: [{ type: 'text', text: 'item 2' }]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
html: `
|
||||
<ol start='2'>
|
||||
<li>item 1</li>
|
||||
<li>item 2</li>
|
||||
</ol>`
|
||||
});
|
||||
});
|
||||
|
||||
it('tasks', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'list',
|
||||
ordered: false,
|
||||
start: '',
|
||||
loose: false,
|
||||
items: [
|
||||
{
|
||||
task: true,
|
||||
checked: false,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 1',
|
||||
tokens: [{ type: 'text', text: 'item 1' }]
|
||||
}]
|
||||
},
|
||||
{
|
||||
task: true,
|
||||
checked: true,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 2',
|
||||
tokens: [{ type: 'text', text: 'item 2' }]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
html: `
|
||||
<ul>
|
||||
<li><input disabled type="checkbox"> item 1</li>
|
||||
<li><input checked disabled type="checkbox"> item 2</li>
|
||||
</ul>`
|
||||
});
|
||||
});
|
||||
|
||||
it('loose', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{
|
||||
type: 'list',
|
||||
ordered: false,
|
||||
start: '',
|
||||
loose: true,
|
||||
items: [
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 1',
|
||||
tokens: [{ type: 'text', text: 'item 1' }]
|
||||
}]
|
||||
},
|
||||
{
|
||||
task: false,
|
||||
checked: undefined,
|
||||
tokens: [{
|
||||
type: 'text',
|
||||
text: 'item 2',
|
||||
tokens: [{ type: 'text', text: 'item 2' }]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
html: `
|
||||
<ul>
|
||||
<li><p>item 1</p></li>
|
||||
<li><p>item 2</p></li>
|
||||
</ul>`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('html', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'html',
|
||||
text: '<div>html</div>'
|
||||
}],
|
||||
html: '<div>html</div>'
|
||||
});
|
||||
});
|
||||
|
||||
it('paragraph', async() => {
|
||||
await expectHtml({
|
||||
tokens: [{
|
||||
type: 'paragraph',
|
||||
text: 'paragraph 1',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'paragraph 1' }
|
||||
]
|
||||
}],
|
||||
html: '<p>paragraph 1</p>'
|
||||
});
|
||||
});
|
||||
|
||||
it('text', async() => {
|
||||
await expectHtml({
|
||||
tokens: [
|
||||
{ type: 'text', text: 'text 1' },
|
||||
{ type: 'text', text: 'text 2' }
|
||||
],
|
||||
html: '<p>text 1\ntext 2</p>'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inline', () => {
|
||||
it('escape', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{ type: 'escape', text: '>' }
|
||||
],
|
||||
html: '>'
|
||||
});
|
||||
});
|
||||
|
||||
it('html', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{ type: 'html', text: '<div>' },
|
||||
{ type: 'text', text: 'html' },
|
||||
{ type: 'html', text: '</div>' }
|
||||
],
|
||||
html: '<div>html</div>'
|
||||
});
|
||||
});
|
||||
|
||||
it('link', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'link',
|
||||
text: 'link',
|
||||
href: 'https://example.com',
|
||||
title: 'title',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'link' }
|
||||
]
|
||||
}
|
||||
],
|
||||
html: '<a href="https://example.com" title="title">link</a>'
|
||||
});
|
||||
});
|
||||
|
||||
it('image', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'image',
|
||||
text: 'image',
|
||||
href: 'image.png',
|
||||
title: 'title'
|
||||
}
|
||||
],
|
||||
html: '<img src="image.png" alt="image" title="title">'
|
||||
});
|
||||
});
|
||||
|
||||
it('strong', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'strong',
|
||||
text: 'strong',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'strong' }
|
||||
]
|
||||
}
|
||||
],
|
||||
html: '<strong>strong</strong>'
|
||||
});
|
||||
});
|
||||
|
||||
it('em', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'em',
|
||||
text: 'em',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'em' }
|
||||
]
|
||||
}
|
||||
],
|
||||
html: '<em>em</em>'
|
||||
});
|
||||
});
|
||||
|
||||
it('codespan', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'codespan',
|
||||
text: 'code'
|
||||
}
|
||||
],
|
||||
html: '<code>code</code>'
|
||||
});
|
||||
});
|
||||
|
||||
it('br', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [{
|
||||
type: 'br'
|
||||
}],
|
||||
html: '<br />'
|
||||
});
|
||||
});
|
||||
|
||||
it('del', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{
|
||||
type: 'del',
|
||||
text: 'del',
|
||||
tokens: [
|
||||
{ type: 'text', text: 'del' }
|
||||
]
|
||||
}
|
||||
],
|
||||
html: '<del>del</del>'
|
||||
});
|
||||
});
|
||||
|
||||
it('text', async() => {
|
||||
await expectHtml({
|
||||
inline: true,
|
||||
tokens: [
|
||||
{ type: 'text', text: 'text 1' },
|
||||
{ type: 'text', text: 'text 2' }
|
||||
],
|
||||
html: 'text 1text 2'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -58,6 +58,11 @@ describe('Test slugger functionality', () => {
|
||||
const slugger = new marked.Slugger();
|
||||
expect(slugger.slug('file.txt')).toBe('filetxt');
|
||||
});
|
||||
|
||||
it('should remove html tags', () => {
|
||||
const slugger = new marked.Slugger();
|
||||
expect(slugger.slug('<em>html</em>')).toBe('html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test paragraph token type', () => {
|
||||
@ -67,8 +72,8 @@ describe('Test paragraph token type', () => {
|
||||
const tokens = marked.lexer(md);
|
||||
|
||||
expect(tokens[0].type).toBe('paragraph');
|
||||
expect(tokens[3].type).toBe('paragraph');
|
||||
expect(tokens[7].type).toBe('text');
|
||||
expect(tokens[2].tokens[0].type).toBe('paragraph');
|
||||
expect(tokens[3].items[0].tokens[0].type).toBe('text');
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user