123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- // GFM table, non-standard
- 'use strict';
- var isSpace = require('../common/utils').isSpace;
- function getLine(state, line) {
- var pos = state.bMarks[line] + state.blkIndent,
- max = state.eMarks[line];
- return state.src.substr(pos, max - pos);
- }
- function escapedSplit(str) {
- var result = [],
- pos = 0,
- max = str.length,
- ch,
- escapes = 0,
- lastPos = 0,
- backTicked = false,
- lastBackTick = 0;
- ch = str.charCodeAt(pos);
- while (pos < max) {
- if (ch === 0x60/* ` */) {
- if (backTicked) {
- // make \` close code sequence, but not open it;
- // the reason is: `\` is correct code block
- backTicked = false;
- lastBackTick = pos;
- } else if (escapes % 2 === 0) {
- backTicked = true;
- lastBackTick = pos;
- }
- } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) {
- result.push(str.substring(lastPos, pos));
- lastPos = pos + 1;
- }
- if (ch === 0x5c/* \ */) {
- escapes++;
- } else {
- escapes = 0;
- }
- pos++;
- // If there was an un-closed backtick, go back to just after
- // the last backtick, but as if it was a normal character
- if (pos === max && backTicked) {
- backTicked = false;
- pos = lastBackTick + 1;
- }
- ch = str.charCodeAt(pos);
- }
- result.push(str.substring(lastPos));
- return result;
- }
- module.exports = function table(state, startLine, endLine, silent) {
- var ch, lineText, pos, i, nextLine, columns, columnCount, token,
- aligns, t, tableLines, tbodyLines;
- // should have at least two lines
- if (startLine + 2 > endLine) { return false; }
- nextLine = startLine + 1;
- if (state.sCount[nextLine] < state.blkIndent) { return false; }
- // if it's indented more than 3 spaces, it should be a code block
- if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; }
- // first character of the second line should be '|', '-', ':',
- // and no other characters are allowed but spaces;
- // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
- pos = state.bMarks[nextLine] + state.tShift[nextLine];
- if (pos >= state.eMarks[nextLine]) { return false; }
- ch = state.src.charCodeAt(pos++);
- if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; }
- while (pos < state.eMarks[nextLine]) {
- ch = state.src.charCodeAt(pos);
- if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; }
- pos++;
- }
- lineText = getLine(state, startLine + 1);
- columns = lineText.split('|');
- aligns = [];
- for (i = 0; i < columns.length; i++) {
- t = columns[i].trim();
- if (!t) {
- // allow empty columns before and after table, but not in between columns;
- // e.g. allow ` |---| `, disallow ` ---||--- `
- if (i === 0 || i === columns.length - 1) {
- continue;
- } else {
- return false;
- }
- }
- if (!/^:?-+:?$/.test(t)) { return false; }
- if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
- aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');
- } else if (t.charCodeAt(0) === 0x3A/* : */) {
- aligns.push('left');
- } else {
- aligns.push('');
- }
- }
- lineText = getLine(state, startLine).trim();
- if (lineText.indexOf('|') === -1) { return false; }
- if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
- columns = escapedSplit(lineText.replace(/^\||\|$/g, ''));
- // header row will define an amount of columns in the entire table,
- // and align row shouldn't be smaller than that (the rest of the rows can)
- columnCount = columns.length;
- if (columnCount > aligns.length) { return false; }
- if (silent) { return true; }
- token = state.push('table_open', 'table', 1);
- token.map = tableLines = [ startLine, 0 ];
- token = state.push('thead_open', 'thead', 1);
- token.map = [ startLine, startLine + 1 ];
- token = state.push('tr_open', 'tr', 1);
- token.map = [ startLine, startLine + 1 ];
- for (i = 0; i < columns.length; i++) {
- token = state.push('th_open', 'th', 1);
- token.map = [ startLine, startLine + 1 ];
- if (aligns[i]) {
- token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
- }
- token = state.push('inline', '', 0);
- token.content = columns[i].trim();
- token.map = [ startLine, startLine + 1 ];
- token.children = [];
- token = state.push('th_close', 'th', -1);
- }
- token = state.push('tr_close', 'tr', -1);
- token = state.push('thead_close', 'thead', -1);
- token = state.push('tbody_open', 'tbody', 1);
- token.map = tbodyLines = [ startLine + 2, 0 ];
- for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
- if (state.sCount[nextLine] < state.blkIndent) { break; }
- lineText = getLine(state, nextLine).trim();
- if (lineText.indexOf('|') === -1) { break; }
- if (state.sCount[nextLine] - state.blkIndent >= 4) { break; }
- columns = escapedSplit(lineText.replace(/^\||\|$/g, ''));
- token = state.push('tr_open', 'tr', 1);
- for (i = 0; i < columnCount; i++) {
- token = state.push('td_open', 'td', 1);
- if (aligns[i]) {
- token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
- }
- token = state.push('inline', '', 0);
- token.content = columns[i] ? columns[i].trim() : '';
- token.children = [];
- token = state.push('td_close', 'td', -1);
- }
- token = state.push('tr_close', 'tr', -1);
- }
- token = state.push('tbody_close', 'tbody', -1);
- token = state.push('table_close', 'table', -1);
- tableLines[1] = tbodyLines[1] = nextLine;
- state.line = nextLine;
- return true;
- };
|