Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed
https://markdown-it.github.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
4.2 KiB
175 lines
4.2 KiB
// Parser state class
|
|
|
|
'use strict';
|
|
|
|
var Token = require('../token');
|
|
|
|
|
|
function StateBlock(src, md, env, tokens) {
|
|
var ch, s, start, pos, len, indent, indent_found;
|
|
|
|
this.src = src;
|
|
|
|
// link to parser instance
|
|
this.md = md;
|
|
|
|
this.env = env;
|
|
|
|
//
|
|
// Internal state vartiables
|
|
//
|
|
|
|
this.tokens = tokens;
|
|
|
|
this.bMarks = []; // line begin offsets for fast jumps
|
|
this.eMarks = []; // line end offsets for fast jumps
|
|
this.tShift = []; // indent for each line
|
|
|
|
// block parser variables
|
|
this.blkIndent = 0; // required block content indent
|
|
// (for example, if we are in list)
|
|
this.line = 0; // line index in src
|
|
this.lineMax = 0; // lines count
|
|
this.tight = false; // loose/tight mode for lists
|
|
this.parentType = 'root'; // if `list`, block parser stops on two newlines
|
|
this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any)
|
|
|
|
this.level = 0;
|
|
|
|
// renderer
|
|
this.result = '';
|
|
|
|
// Create caches
|
|
// Generate markers.
|
|
s = this.src;
|
|
indent = 0;
|
|
indent_found = false;
|
|
|
|
for (start = pos = indent = 0, len = s.length; pos < len; pos++) {
|
|
ch = s.charCodeAt(pos);
|
|
|
|
if (!indent_found) {
|
|
if (ch === 0x20/* space */) {
|
|
indent++;
|
|
continue;
|
|
} else {
|
|
indent_found = true;
|
|
}
|
|
}
|
|
|
|
if (ch === 0x0A || pos === len - 1) {
|
|
if (ch !== 0x0A) { pos++; }
|
|
this.bMarks.push(start);
|
|
this.eMarks.push(pos);
|
|
this.tShift.push(indent);
|
|
|
|
indent_found = false;
|
|
indent = 0;
|
|
start = pos + 1;
|
|
}
|
|
}
|
|
|
|
// Push fake entry to simplify cache bounds checks
|
|
this.bMarks.push(s.length);
|
|
this.eMarks.push(s.length);
|
|
this.tShift.push(0);
|
|
|
|
this.lineMax = this.bMarks.length - 1; // don't count last fake line
|
|
}
|
|
|
|
// Push new token to "stream".
|
|
//
|
|
StateBlock.prototype.push = function (type, tag, nesting) {
|
|
var token = new Token(type, tag, nesting);
|
|
token.block = true;
|
|
|
|
if (nesting < 0) { this.level--; }
|
|
token.level = this.level;
|
|
if (nesting > 0) { this.level++; }
|
|
|
|
this.tokens.push(token);
|
|
return token;
|
|
};
|
|
|
|
StateBlock.prototype.isEmpty = function isEmpty(line) {
|
|
return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];
|
|
};
|
|
|
|
StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) {
|
|
for (var max = this.lineMax; from < max; from++) {
|
|
if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {
|
|
break;
|
|
}
|
|
}
|
|
return from;
|
|
};
|
|
|
|
// Skip spaces from given position.
|
|
StateBlock.prototype.skipSpaces = function skipSpaces(pos) {
|
|
for (var max = this.src.length; pos < max; pos++) {
|
|
if (this.src.charCodeAt(pos) !== 0x20/* space */) { break; }
|
|
}
|
|
return pos;
|
|
};
|
|
|
|
// Skip char codes from given position
|
|
StateBlock.prototype.skipChars = function skipChars(pos, code) {
|
|
for (var max = this.src.length; pos < max; pos++) {
|
|
if (this.src.charCodeAt(pos) !== code) { break; }
|
|
}
|
|
return pos;
|
|
};
|
|
|
|
// Skip char codes reverse from given position - 1
|
|
StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) {
|
|
if (pos <= min) { return pos; }
|
|
|
|
while (pos > min) {
|
|
if (code !== this.src.charCodeAt(--pos)) { return pos + 1; }
|
|
}
|
|
return pos;
|
|
};
|
|
|
|
// cut lines range from source.
|
|
StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) {
|
|
var i, first, last, queue, shift,
|
|
line = begin;
|
|
|
|
if (begin >= end) {
|
|
return '';
|
|
}
|
|
|
|
// Opt: don't use push queue for single line;
|
|
if (line + 1 === end) {
|
|
first = this.bMarks[line] + Math.min(this.tShift[line], indent);
|
|
last = keepLastLF ? this.bMarks[end] : this.eMarks[end - 1];
|
|
return this.src.slice(first, last);
|
|
}
|
|
|
|
queue = new Array(end - begin);
|
|
|
|
for (i = 0; line < end; line++, i++) {
|
|
shift = this.tShift[line];
|
|
if (shift > indent) { shift = indent; }
|
|
if (shift < 0) { shift = 0; }
|
|
|
|
first = this.bMarks[line] + shift;
|
|
|
|
if (line + 1 < end || keepLastLF) {
|
|
// No need for bounds check because we have fake entry on tail.
|
|
last = this.eMarks[line] + 1;
|
|
} else {
|
|
last = this.eMarks[line];
|
|
}
|
|
|
|
queue[i] = this.src.slice(first, last);
|
|
}
|
|
|
|
return queue.join('');
|
|
};
|
|
|
|
// re-export Token class to use in block rules
|
|
StateBlock.prototype.Token = Token;
|
|
|
|
|
|
module.exports = StateBlock;
|
|
|