287 lines
7.7 KiB
JavaScript
287 lines
7.7 KiB
JavaScript
import { Renderer } from './Renderer.js';
|
|
import { TextRenderer } from './TextRenderer.js';
|
|
import { Slugger } from './Slugger.js';
|
|
import { defaults } from './defaults.js';
|
|
import {
|
|
unescape
|
|
} from './helpers.js';
|
|
|
|
/**
|
|
* Parsing & Compiling
|
|
*/
|
|
export class Parser {
|
|
constructor(options) {
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* Static Parse Method
|
|
*/
|
|
static parse(tokens, options) {
|
|
const parser = new Parser(options);
|
|
return parser.parse(tokens);
|
|
}
|
|
|
|
/**
|
|
* Static Parse Inline Method
|
|
*/
|
|
static parseInline(tokens, options) {
|
|
const parser = new Parser(options);
|
|
return parser.parseInline(tokens);
|
|
}
|
|
|
|
/**
|
|
* Parse Loop
|
|
*/
|
|
parse(tokens, top = true) {
|
|
let out = '',
|
|
i,
|
|
j,
|
|
k,
|
|
l2,
|
|
l3,
|
|
row,
|
|
cell,
|
|
header,
|
|
body,
|
|
token,
|
|
ordered,
|
|
start,
|
|
loose,
|
|
itemBody,
|
|
item,
|
|
checked,
|
|
task,
|
|
checkbox,
|
|
ret;
|
|
|
|
const l = tokens.length;
|
|
for (i = 0; i < l; i++) {
|
|
token = tokens[i];
|
|
|
|
// Run any renderer extensions
|
|
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
|
ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
|
|
if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(token.type)) {
|
|
out += ret || '';
|
|
continue;
|
|
}
|
|
}
|
|
|
|
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.header[j].tokens),
|
|
{ header: true, align: token.align[j] }
|
|
);
|
|
}
|
|
header += this.renderer.tablerow(cell);
|
|
|
|
body = '';
|
|
l2 = token.rows.length;
|
|
for (j = 0; j < l2; j++) {
|
|
row = token.rows[j];
|
|
|
|
cell = '';
|
|
l3 = row.length;
|
|
for (k = 0; k < l3; k++) {
|
|
cell += this.renderer.tablecell(
|
|
this.parseInline(row[k].tokens),
|
|
{ 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.length > 0 && item.tokens[0].type === 'paragraph') {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Parse Inline Tokens
|
|
*/
|
|
parseInline(tokens, renderer) {
|
|
renderer = renderer || this.renderer;
|
|
let out = '',
|
|
i,
|
|
token,
|
|
ret;
|
|
|
|
const l = tokens.length;
|
|
for (i = 0; i < l; i++) {
|
|
token = tokens[i];
|
|
|
|
// Run any renderer extensions
|
|
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
|
ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
|
|
if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) {
|
|
out += ret || '';
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (token.type) {
|
|
case 'escape': {
|
|
out += renderer.text(token.text);
|
|
break;
|
|
}
|
|
case 'html': {
|
|
out += renderer.html(token.text);
|
|
break;
|
|
}
|
|
case 'link': {
|
|
out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
|
|
break;
|
|
}
|
|
case 'image': {
|
|
out += renderer.image(token.href, token.title, token.text);
|
|
break;
|
|
}
|
|
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 {
|
|
throw new Error(errMsg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
}
|