2025-05-12 05:38:44 +09:00

294 lines
6.8 KiB
JavaScript

var _utils = require('../utils');
var escape = _utils.escape;
var noop = _utils.noop;
var inline = require('../rules/inline');
var Renderer = require('../renderer');
var defaultOptions = require('./options');
var isHTMLBlock = require('./html_blocks');
/**
* Inline Lexer & Compiler
*/
function InlineLexer(links, options, renderer) {
this.options = options || defaultOptions;
this.links = links;
this.rules = inline.normal;
this.renderer = renderer
if (!this.links) {
throw new
Error('Tokens array requires a `links` property.');
}
if (this.options.gfm) {
if (this.options.breaks) {
this.rules = inline.breaks;
} else {
this.rules = inline.gfm;
}
} else if (this.options.pedantic) {
this.rules = inline.pedantic;
}
// Is mathjax disabled ?
if (!this.options.mathjax) {
this.rules.math = noop;
}
}
/**
* Expose Inline Rules
*/
InlineLexer.rules = inline;
/**
* Static Lexing/Compiling Method
*/
InlineLexer.output = function(src, links, options) {
var inline = new InlineLexer(links, options, new Renderer());
return inline.output(src);
};
InlineLexer.prototype.escape = function(html, encode) {
// Handle escaping being turned off
if(this.options && this.options.escape === false) {
return html;
}
return escape(html, encode);
};
/**
* Lexing/Compiling
*/
InlineLexer.prototype.output = function(src) {
var out = ''
, link
, text
, href
, cap;
while (src) {
// escape
if (cap = this.rules.escape.exec(src)) {
src = src.substring(cap[0].length);
out += cap[1];
continue;
}
// autolink
if (cap = this.rules.autolink.exec(src)) {
src = src.substring(cap[0].length);
if (cap[2] === '@') {
text = cap[1].charAt(6) === ':'
? this.mangle(cap[1].substring(7))
: this.mangle(cap[1]);
href = this.mangle('mailto:') + text;
} else {
text = this.escape(cap[1]);
href = text;
}
out += this.renderer.link(href, null, text);
continue;
}
// url (gfm)
if (!this.inLink && (cap = this.rules.url.exec(src))) {
src = src.substring(cap[0].length);
text = this.escape(cap[1]);
href = text;
out += this.renderer.link(href, null, text);
continue;
}
// html
if (cap = this.rules.html.exec(src)) {
// Found a link
if(cap[1] === 'a' && cap[2] && !this.inLink) {
// Opening tag
out += cap[0].substring(0, cap[0].indexOf(cap[2]));
this.inLink = true;
// In between the tag
out += this.output(cap[2]);
this.inLink = false;
// Outer tag
out += cap[0].substring(cap[0].indexOf(cap[2])+cap[2].length);
// Advance parser
src = src.substring(cap[0].length);
continue;
}
// Found HTML that we should parse
if(cap[1] && !isHTMLBlock(cap[1]) && cap[2]) {
// Opening tag
out += cap[0].substring(0, cap[0].indexOf(cap[2]));
// In between the tag
out += this.output(cap[2]);
// Outer tag
out += cap[0].substring(cap[0].indexOf(cap[2])+cap[2].length);
// Advance parser
src = src.substring(cap[0].length);
continue;
}
// Any other HTML
src = src.substring(cap[0].length);
out += cap[0];
continue;
}
// link
if (cap = this.rules.link.exec(src)) {
src = src.substring(cap[0].length);
this.inLink = true;
out += this.outputLink(cap, {
href: cap[2],
title: cap[3]
});
this.inLink = false;
continue;
}
// reffn
if ((cap = this.rules.reffn.exec(src))) {
src = src.substring(cap[0].length);
out += this.renderer.reffn(cap[1]);
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[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[2] || cap[1]));
continue;
}
// code
if (cap = this.rules.code.exec(src)) {
src = src.substring(cap[0].length);
out += this.renderer.codespan(this.escape(cap[2], true));
continue;
}
// math
if (cap = this.rules.math.exec(src)) {
src = src.substring(cap[0].length);
out += this.renderer.math(cap[1], 'math/tex', false); //FIXME: filter <script> & </script>
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;
}
// text
if (cap = this.rules.text.exec(src)) {
src = src.substring(cap[0].length);
out += this.escape(this.smartypants(cap[0]));
continue;
}
if (src) {
throw new
Error('Infinite loop on byte: ' + src.charCodeAt(0));
}
}
return out;
};
/**
* Compile Link
*/
InlineLexer.prototype.outputLink = function(cap, link) {
var href = this.escape(link.href)
, title = link.title ? this.escape(link.title) : null;
return cap[0].charAt(0) !== '!'
? this.renderer.link(href, title, this.output(cap[1]))
: this.renderer.image(href, title, this.escape(cap[1]));
};
/**
* Smartypants Transformations
*/
InlineLexer.prototype.smartypants = function(text) {
if (!this.options.smartypants) return text;
return text
// em-dashes
.replace(/--/g, '\u2014')
// 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
*/
InlineLexer.prototype.mangle = function(text) {
var out = ''
, l = text.length
, 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;
};
module.exports = InlineLexer;