markdown-it-multimd-table
Advanced tools
Comparing version 3.2.3 to 4.0.0
@@ -24,15 +24,14 @@ { | ||
], | ||
"dependencies":{ | ||
"markdown-it": "^5.0.3" | ||
"dependencies": { | ||
"markdown-it": "^8.4.2" | ||
}, | ||
"devDependencies": { | ||
"browserify": "*", | ||
"coveralls": "^2.11.2", | ||
"eslint": "^3.19.0", | ||
"eslint-plugin-nodeca": "^1.0.0", | ||
"istanbul": "*", | ||
"markdown-it-testgen": "~0.1.0", | ||
"mocha": "*", | ||
"uglify-js": "^2.7.3" | ||
"browserify": "^16.3.0", | ||
"coveralls": "^3.0.4", | ||
"eslint": "^6.0.1", | ||
"istanbul": "^0.4.5", | ||
"markdown-it-testgen": "^0.1.3", | ||
"mocha": "^6.1.4", | ||
"terser": "^4.1.2" | ||
} | ||
} |
@@ -1,224 +0,191 @@ | ||
/*! markdown-it-multimd-table 3.2.3 https://github.com/RedBug312/markdown-it-multimd-table @license MIT */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.markdownitMultimdTable = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
/*! markdown-it-multimd-table 4.0.0 https://github.com/RedBug312/markdown-it-multimd-table @license MIT */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.markdownitMultimdTable = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
'use strict'; | ||
module.exports = function multimd_table_plugin(md, pluginOptions) { | ||
pluginOptions = pluginOptions || {}; | ||
// constructor | ||
function getLine(state, line) { | ||
var pos = state.bMarks[line] + state.blkIndent, | ||
max = state.eMarks[line]; | ||
return state.src.slice(pos, max); | ||
} | ||
function DFA() { | ||
// alphabets are encoded by numbers in 16^N form, presenting its precedence | ||
this.__highest_alphabet__ = 0x0; | ||
this.__match_alphabets__ = {}; | ||
// states are union (bitwise OR) of its accepted alphabets | ||
this.__initial_state__ = 0x0; | ||
this.__accept_states__ = {}; | ||
// transitions are in the form: {prev_state: {alphabet: next_state}} | ||
this.__transitions__ = {}; | ||
// actions take two parameters: step (line number), prev_state and alphabet | ||
this.__actions__ = {}; | ||
} | ||
function escapedSplit(str) { | ||
var result = [], | ||
max = str.length, | ||
lastPos = 0, | ||
escaped = false, | ||
backTicked = false; | ||
// setters | ||
for (var pos = 0; pos < max; pos++) { | ||
switch (str.charCodeAt(pos)) { | ||
case 0x5c /* \ */: | ||
escaped = true; | ||
break; | ||
case 0x60 /* ` */: | ||
if (backTicked || !escaped) { | ||
/* make \` closes the code sequence, but not open it; | ||
the reason is that `\` is correct code block */ | ||
backTicked = !backTicked; | ||
} | ||
escaped = false; | ||
break; | ||
case 0x7c /* | */: | ||
if (!backTicked && !escaped) { | ||
result.push(str.slice(lastPos, pos)); | ||
lastPos = pos + 1; | ||
} | ||
escaped = false; | ||
break; | ||
default: | ||
escaped = false; | ||
break; | ||
} | ||
} | ||
DFA.prototype.set_highest_alphabet = function (alphabet) { | ||
this.__highest_alphabet__ = alphabet; | ||
}; | ||
result.push(str.slice(lastPos)); | ||
DFA.prototype.set_match_alphabets = function (matches) { | ||
this.__match_alphabets__ = matches; | ||
}; | ||
return result; | ||
DFA.prototype.set_initial_state = function (initial) { | ||
this.__initial_state__ = initial; | ||
}; | ||
DFA.prototype.set_accept_states = function (accepts) { | ||
for (var i = 0; i < accepts.length; i++) { | ||
this.__accept_states__[accepts[i]] = true; | ||
} | ||
}; | ||
function countColspan(columns) { | ||
var emptyCount = 0, | ||
colspans = []; | ||
DFA.prototype.set_transitions = function (transitions) { | ||
this.__transitions__ = transitions; | ||
}; | ||
for (var i = columns.length - 1; i >= 0; i--) { | ||
if (columns[i]) { | ||
colspans.unshift(emptyCount + 1); | ||
emptyCount = 0; | ||
} else { | ||
emptyCount++; | ||
} | ||
DFA.prototype.set_actions = function (actions) { | ||
this.__actions__ = actions; | ||
}; | ||
DFA.prototype.update_transition = function (state, alphabets) { | ||
this.__transitions__[state] = Object.assign( | ||
this.__transitions__[state] || Object(), alphabets | ||
); | ||
}; | ||
// methods | ||
DFA.prototype.execute = function (start, end) { | ||
var state, step, alphabet; | ||
for (state = this.__initial_state__, step = start; state && step < end; step++) { | ||
for (alphabet = this.__highest_alphabet__; alphabet > 0x0; alphabet >>= 4) { | ||
if ((state & alphabet) | ||
&& this.__match_alphabets__[alphabet].call(this, step, state, alphabet)) { break; } | ||
} | ||
if (emptyCount > 0) { | ||
colspans.unshift(emptyCount + 1); | ||
} | ||
return colspans; | ||
this.__actions__(step, state, alphabet); | ||
if (alphabet === 0x0) { break; } | ||
state = this.__transitions__[state][alphabet] || 0x0; | ||
} | ||
return !!this.__accept_states__[state]; | ||
}; | ||
function caption(state, lineText, lineNum, silent) { | ||
var result = lineText.match(/^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/); | ||
if (!result) { return false; } | ||
if (silent) { return true; } | ||
module.exports = DFA; | ||
var captionInfo = { caption: null, label: null }; | ||
captionInfo.content = result[1]; | ||
captionInfo.label = result[2] || result[1]; | ||
/* vim: set ts=2 sw=2 et: */ | ||
var token; | ||
token = state.push('caption_open', 'caption', 1); | ||
token.map = [ lineNum, lineNum + 1 ]; | ||
token.attrs = [ [ 'id', captionInfo.label.toLowerCase().replace(/\W+/g, '') ] ]; | ||
},{}],2:[function(require,module,exports){ | ||
'use strict'; | ||
var DFA = require('./lib/dfa.js'); | ||
token = state.push('inline', '', 0); | ||
token.content = captionInfo.content; | ||
token.map = [ lineNum, lineNum + 1 ]; | ||
token.children = []; | ||
module.exports = function multimd_table_plugin(md, options) { | ||
// TODO be consistent with markdown-it method | ||
options = options || {}; | ||
token = state.push('caption_close', 'caption', -1); | ||
function scan_bound_indices(state, line) { | ||
var start = state.bMarks[line], /* no tShift to detect \n */ | ||
max = state.eMarks[line], | ||
bounds = [], pos, | ||
escape = false, code = false; | ||
return captionInfo; | ||
} | ||
/* Scan for valid pipe character position */ | ||
for (pos = start; pos < max; pos++) { | ||
switch (state.src.charCodeAt(pos)) { | ||
case 0x5c /* \ */: | ||
escape = true; break; | ||
case 0x60 /* ` */: | ||
/* make \` closes the code sequence, but not open it; | ||
the reason is that `\` is correct code block */ | ||
if (code || !escape) { code = !code; } | ||
if (state.src.charCodeAt(pos - 1) === 0x60) { code = false; } | ||
escape = false; break; | ||
case 0x7c /* | */: | ||
if (!code && !escape) { bounds.push(pos); } | ||
escape = false; break; | ||
default: | ||
escape = false; break; | ||
} | ||
} | ||
if (bounds.length === 0) return bounds; | ||
function appendRowToken(state, content, startLine, endLine) { | ||
var linesCount, blockParser, tmpState, token; | ||
linesCount = content.split(/\n/).length; | ||
/* Pad in newline characters on last and this line */ | ||
if (bounds[0] > start) { bounds.unshift(start - 1); } | ||
if (bounds[bounds.length - 1] < max - 1) { bounds.push(max); } | ||
if (linesCount > 1) { | ||
// Multiline content => subparsing as a block to support lists | ||
blockParser = state.md.block; | ||
tmpState = new blockParser.State(content, state.md, state.env, state.tokens); | ||
blockParser.tokenize(tmpState, 0, linesCount); // appends to state.tokens | ||
} else { | ||
token = state.push('inline', '', 0); | ||
token.content = content; | ||
token.map = [ startLine, endLine ]; | ||
token.children = []; | ||
} | ||
return bounds; | ||
} | ||
function tableRow(state, lineText, lineNum, silent, separatorInfo, rowType, rowspanState) { | ||
var rowInfo, columns; | ||
rowInfo = { colspans: null, columns: null, extractedTextLinesCount: 1 }; | ||
columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1')); | ||
function table_caption(state, silent, line) { | ||
var start = state.bMarks[line] + state.tShift[line], | ||
max = state.eMarks[line], | ||
capRE = /^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/, | ||
matches = state.src.slice(start, max).match(capRE), | ||
meta = {}; | ||
// lineText does not contain valid pipe character | ||
if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; } | ||
if (silent) { return true; } | ||
if (!matches) { return false; } | ||
if (silent) { return true; } | ||
// TODO eliminate capRE by simple checking | ||
// Multiline feature | ||
if (pluginOptions.enableMultilineRows && lineText.slice(-1) === '\\') { | ||
var lineTextNext, columnsNext, EndOfMultilines; | ||
var trimItself = Function.prototype.call.bind(String.prototype.trim); // equal to (x => x.trim()) | ||
columns = escapedSplit(lineText.replace(/\\$/, '').replace(/^\||([^\\])\|$/g, '$1')); | ||
var initialIndent = /^\s*/.exec(columns[0])[0]; | ||
var trimRegex = new RegExp('^' + initialIndent + '|\\s+$', 'g'); | ||
columns = columns.map(trimItself); | ||
do { | ||
lineTextNext = getLine(state, lineNum + rowInfo.extractedTextLinesCount); | ||
columnsNext = escapedSplit(lineTextNext.replace(/\\$/, '').replace(/^\||([^\\])\|$/g, '$1')); | ||
EndOfMultilines = lineTextNext.slice(-1) !== '\\'; | ||
meta.text = matches[1]; | ||
meta.label = matches[2] || matches[1]; | ||
meta.label = meta.label.toLowerCase().replace(/\W+/g, ''); | ||
if (columnsNext.length === 1 && !/^\||[^\\]\|$|\\$/.test(lineTextNext)) { return false; } | ||
if (columnsNext.length !== columns.length && !EndOfMultilines) { return false; } | ||
return meta; | ||
} | ||
for (var j = 0; j < columnsNext.length; j++) { | ||
columns[j] = columns[j] || ''; | ||
columns[j] += '\n' + columnsNext[j].replace(trimRegex, ''); | ||
} | ||
rowInfo.extractedTextLinesCount += 1; | ||
function table_row(state, silent, line) { | ||
var bounds = scan_bound_indices(state, line), | ||
meta = {}, start, pos, oldMax; | ||
} while (!EndOfMultilines); | ||
} | ||
if (bounds.length < 2) { return false; } | ||
if (silent) { return true; } | ||
// Fill in HTML <tr> elements | ||
var isValidColumn = RegExp.prototype.test.bind(/[^\n]/); // equal to (s => /[^\n]/.test(s)) | ||
rowInfo.columns = columns.filter(isValidColumn); | ||
rowInfo.colspans = countColspan(columns.map(isValidColumn)); | ||
meta.bounds = bounds; | ||
var token = state.push('tr_open', 'tr', 1); | ||
token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ]; | ||
for (var i = 0, col = 0; | ||
i < rowInfo.columns.length && col < separatorInfo.aligns.length; | ||
col += rowInfo.colspans[i], i++) { | ||
if (pluginOptions.enableRowspan && | ||
rowspanState && rowspanState[i] && | ||
/^\s*\^\^\s*$/.test(rowInfo.columns[i])) { | ||
var rowspanAttr = rowspanState[i].attrs.find(function (attr) { | ||
return attr[0] === 'rowspan'; | ||
}); | ||
if (!rowspanAttr) { | ||
rowspanAttr = [ 'rowspan', 1 ]; | ||
rowspanState[i].attrs.push(rowspanAttr); | ||
} | ||
rowspanAttr[1]++; | ||
continue; | ||
/* Multiline. Scan boundaries again since it's very complicated */ | ||
if (options.multiline) { | ||
start = state.bMarks[line] + state.tShift[line]; | ||
pos = state.eMarks[line] - 1; /* where backslash should be */ | ||
meta.multiline = (state.src.charCodeAt(pos) === 0x5C/* \ */); | ||
if (meta.multiline) { | ||
oldMax = state.eMarks[line]; | ||
state.eMarks[line] = state.skipSpacesBack(pos, start); | ||
meta.bounds = scan_bound_indices(state, line); | ||
state.eMarks[line] = oldMax; | ||
} | ||
token = state.push(rowType + '_open', rowType, 1); | ||
token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ]; | ||
token.attrs = []; | ||
rowspanState[i] = { | ||
attrs: token.attrs | ||
}; | ||
if (separatorInfo.aligns[col]) { | ||
token.attrs.push([ 'style', 'text-align:' + separatorInfo.aligns[col] ]); | ||
} | ||
if (separatorInfo.wraps[col]) { | ||
token.attrs.push([ 'class', 'extend' ]); | ||
} | ||
if (rowInfo.colspans[i] > 1) { | ||
token.attrs.push([ 'colspan', rowInfo.colspans[i] ]); | ||
} | ||
appendRowToken(state, rowInfo.columns[i].trim(), lineNum, lineNum + rowInfo.extractedTextLinesCount); | ||
token = state.push(rowType + '_close', rowType, -1); | ||
} | ||
state.push('tr_close', 'tr', -1); | ||
return rowInfo; | ||
return meta; | ||
} | ||
function separator(state, lineText, lineNum, silent) { | ||
// lineText have code indentation | ||
if (state.sCount[lineNum] - state.blkIndent >= 4) { return false; } | ||
function table_separator(state, silent, line) { | ||
var bounds = scan_bound_indices(state, line), | ||
meta = { aligns: [], wraps: [] }, | ||
sepRE = /^:?(-+|=+):?\+?$/, | ||
c, text, align; | ||
// lineText does not contain valid pipe character | ||
var columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1')); | ||
if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; } | ||
/* Only separator needs to check indents */ | ||
if (state.sCount[line] - state.blkIndent >= 4) { return false; } | ||
if (bounds.length === 0) { return false; } | ||
var separatorInfo = { aligns: [], wraps: [] }; | ||
for (c = 0; c < bounds.length - 1; c++) { | ||
text = state.src.slice(bounds[c] + 1, bounds[c + 1]).trim(); | ||
if (!sepRE.test(text)) { return false; } | ||
for (var i = 0; i < columns.length; i++) { | ||
var t = columns[i].trim(); | ||
if (!/^:?(-+|=+):?\+?$/.test(t)) { return false; } | ||
separatorInfo.wraps.push(t.charCodeAt(t.length - 1) === 0x2B/* + */); | ||
if (separatorInfo.wraps[i]) { | ||
t = t.slice(0, -1); | ||
meta.wraps.push(text.charCodeAt(text.length - 1) === 0x2B/* + */); | ||
align = ((text.charCodeAt(0) === 0x3A/* : */) << 4) | | ||
(text.charCodeAt(text.length - 1 - meta.wraps[c]) === 0x3A); | ||
switch (align) { | ||
case 0x00: meta.aligns.push(''); break; | ||
case 0x01: meta.aligns.push('right'); break; | ||
case 0x10: meta.aligns.push('left'); break; | ||
case 0x11: meta.aligns.push('center'); break; | ||
} | ||
switch (((t.charCodeAt(0) === 0x3A /* : */) << 4) + | ||
(t.charCodeAt(t.length - 1) === 0x3A /* : */)) { | ||
case 0x00: separatorInfo.aligns.push(''); break; | ||
case 0x01: separatorInfo.aligns.push('right'); break; | ||
case 0x10: separatorInfo.aligns.push('left'); break; | ||
case 0x11: separatorInfo.aligns.push('center'); break; | ||
} | ||
} | ||
if (silent) { return true; } | ||
return meta; | ||
} | ||
return silent || separatorInfo; | ||
function table_empty(state, silent, line) { | ||
var start = state.bMarks[line] + state.tShift[line], | ||
max = state.eMarks[line]; | ||
return start === max; | ||
} | ||
@@ -228,18 +195,39 @@ | ||
/* Regex pseudo code for table: | ||
* caption? header+ separator (data+ empty)* data+ caption? | ||
* caption? header+ separator (data+ empty)* data+ caption? | ||
* | ||
* We use NFA with precedences to emulate this plugin. | ||
* Noted that separator should have higher precedence than header or data. | ||
* We use DFA to emulate this plugin. Types with lower precedence are | ||
* set-minus from all the formers. Noted that separator should have higher | ||
* precedence than header or data. | ||
* | state | caption separator header data empty | --> lower precedence | ||
* | 0x10100 | 1 0 1 0 0 | | ||
*/ | ||
var tableDFA = new DFA(), | ||
grp = 0x10, mtr = -1, | ||
token, tableToken, trToken, | ||
colspan, leftToken, | ||
rowspan, upTokens = [], | ||
tableLines, tgroupLines, | ||
tag, text, range, r, c, b; | ||
var match = { | ||
0x10000: function (s, l, lt) { return caption(s, lt, l, true); }, | ||
0x01000: function (s, l, lt) { return separator(s, lt, l); }, | ||
0x00100: function (s, l, lt) { return tableRow(s, lt, l, true, null, 'th'); }, | ||
0x00010: function (s, l, lt) { return tableRow(s, lt, l, true, null, 'td'); }, | ||
0x00001: function (s, l, lt) { return !lt; } | ||
}; | ||
var transitions = { | ||
if (startLine + 2 > endLine) { return false; } | ||
/** | ||
* First pass: validate and collect info into table token. IR is stored in | ||
* markdown-it `token.meta` to be pushed later. table/tr open tokens are | ||
* generated here. | ||
*/ | ||
tableToken = new state.Token('table_open', 'table', 1); | ||
tableToken.meta = { sep: null, cap: null, tr: [] }; | ||
tableDFA.set_highest_alphabet(0x10000); | ||
tableDFA.set_initial_state(0x10100); | ||
tableDFA.set_accept_states([ 0x10010, 0x10011, 0x00000 ]); | ||
tableDFA.set_match_alphabets({ | ||
0x10000: table_caption.bind(this, state, true), | ||
0x01000: table_separator.bind(this, state, true), | ||
0x00100: table_row.bind(this, state, true), | ||
0x00010: table_row.bind(this, state, true), | ||
0x00001: table_empty.bind(this, state, true) | ||
}); | ||
tableDFA.set_transitions({ | ||
0x10100: { 0x10000: 0x00100, 0x00100: 0x01100 }, | ||
@@ -250,107 +238,171 @@ 0x00100: { 0x00100: 0x01100 }, | ||
0x10011: { 0x10000: 0x00000, 0x00010: 0x10011, 0x00001: 0x10010 } | ||
}; | ||
/* Check validity; Gather separator informations */ | ||
if (startLine + 2 > endLine) { return false; } | ||
var NFAstate, line, candidate, rowInfo, lineText, separatorInfo; | ||
var captionAtFirst = false; | ||
for (NFAstate = 0x10100, line = startLine; NFAstate && line < endLine; line++) { | ||
lineText = getLine(state, line).trim(); | ||
for (candidate = 0x10000; candidate > 0; candidate >>= 4) { | ||
if (NFAstate & candidate && match[candidate].call(this, state, line, lineText)) { break; } | ||
} | ||
switch (candidate) { | ||
}); | ||
if (options.headerless) { | ||
tableDFA.set_initial_state(0x11100); | ||
tableDFA.update_transition(0x11100, | ||
{ 0x10000: 0x01100, 0x01000: 0x10010, 0x00100: 0x01100 } | ||
); | ||
trToken = new state.Token('table_fake_header_row', 'tr', 1); | ||
trToken.meta = Object(); // avoid trToken.meta.grp throws exception | ||
} | ||
/* Don't mix up DFA `_state` and markdown-it `state` */ | ||
tableDFA.set_actions(function (_line, _state, _type) { | ||
switch (_type) { | ||
case 0x10000: | ||
if (NFAstate === 0x10100) { captionAtFirst = true; } | ||
if (tableToken.meta.cap) { break; } | ||
tableToken.meta.cap = table_caption(state, false, _line); | ||
tableToken.meta.cap.map = [ _line, _line + 1 ]; | ||
tableToken.meta.cap.first = (_line === startLine); | ||
break; | ||
case 0x01000: | ||
separatorInfo = separator(state, lineText, line); | ||
if (silent) { return true; } | ||
tableToken.meta.sep = table_separator(state, false, _line); | ||
tableToken.meta.sep.map = [ _line, _line + 1 ]; | ||
trToken.meta.grp |= 0x01; // previously assigned at case 0x00110 | ||
grp = 0x10; | ||
break; | ||
case 0x00100: | ||
case 0x00010: | ||
trToken = new state.Token('table_row_open', 'tr', 1); | ||
trToken.map = [ _line, _line + 1 ]; | ||
trToken.meta = table_row(state, false, _line); | ||
trToken.meta.type = _type; | ||
trToken.meta.grp = grp; | ||
grp = 0x00; | ||
tableToken.meta.tr.push(trToken); | ||
/* Multiline. Merge trTokens as an entire multiline trToken */ | ||
if (options.multiline) { | ||
if (trToken.meta.multiline && mtr < 0) { | ||
/* Start line of multiline row. mark this trToken */ | ||
mtr = tableToken.meta.tr.length - 1; | ||
} else if (!trToken.meta.multiline && mtr >= 0) { | ||
/* End line of multiline row. merge forward until the marked trToken */ | ||
token = tableToken.meta.tr[mtr]; | ||
token.meta.mbounds = tableToken.meta.tr | ||
.slice(mtr).map(function (tk) { return tk.meta.bounds; }); | ||
token.map[1] = trToken.map[1]; | ||
tableToken.meta.tr = tableToken.meta.tr.slice(0, mtr + 1); | ||
mtr = -1; | ||
} | ||
} | ||
break; | ||
case 0x00001: | ||
trToken.meta.grp |= 0x01; | ||
grp = 0x10; | ||
break; | ||
case 0x00000: | ||
if (NFAstate & 0x00100) { return false; } // separator not reached | ||
} | ||
}); | ||
NFAstate = transitions[NFAstate][candidate] || 0x00000; | ||
} | ||
if (tableDFA.execute(startLine, endLine) === false) { return false; } | ||
// if (!tableToken.meta.sep) { return false; } // always evaluated true | ||
if (!tableToken.meta.tr.length) { return false; } // false under headerless corner case | ||
if (silent) { return true; } | ||
if (!separatorInfo) { return false; } | ||
/* Last data row cannot be detected. not stored to trToken outside? */ | ||
tableToken.meta.tr[tableToken.meta.tr.length - 1].meta.grp |= 0x01; | ||
/* Generate table HTML */ | ||
var token, tableLines, theadLines, tbodyLines; | ||
token = state.push('table_open', 'table', 1); | ||
token.map = tableLines = [ startLine, 0 ]; | ||
/** | ||
* Second pass: actually push the tokens into `state.tokens`. | ||
* thead/tbody/th/td open tokens and all closed tokens are generated here; | ||
* thead/tbody are generally called tgroup; td/th are generally called tcol. | ||
*/ | ||
tableToken.map = tableLines = [ startLine, 0 ]; | ||
tableToken.block = true; | ||
tableToken.level = state.level++; | ||
state.tokens.push(tableToken); | ||
var rowspanState; | ||
for (NFAstate = 0x10100, line = startLine; NFAstate && line < endLine; line++) { | ||
lineText = getLine(state, line).trim(); | ||
if (tableToken.meta.cap) { | ||
token = state.push('caption_open', 'caption', 1); | ||
token.map = tableToken.meta.cap.map; | ||
token.attrs = [ [ 'id', tableToken.meta.cap.label ] ]; | ||
for (candidate = 0x10000; candidate > 0; candidate >>= 4) { | ||
if (NFAstate & candidate && match[candidate].call(this, state, line, lineText)) { break; } | ||
token = state.push('inline', '', 0); | ||
token.content = tableToken.meta.cap.text; | ||
token.map = tableToken.meta.cap.map; | ||
token.children = []; | ||
token = state.push('caption_close', 'caption', -1); | ||
} | ||
for (r = 0; r < tableToken.meta.tr.length; r++) { | ||
leftToken = new state.Token('table_fake_tcol_open', '', 1); | ||
/* Push in thead/tbody and tr open tokens */ | ||
trToken = tableToken.meta.tr[r]; | ||
// console.log(trToken.meta); // for test | ||
if (trToken.meta.grp & 0x10) { | ||
tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody'; | ||
token = state.push('table_group_open', tag, 1); | ||
token.map = tgroupLines = [ trToken.map[0], 0 ]; // array ref | ||
upTokens = []; | ||
} | ||
trToken.block = true; | ||
trToken.level = state.level++; | ||
state.tokens.push(trToken); | ||
switch (candidate) { | ||
case 0x10000: | ||
if (NFAstate !== 0x10100) { // the last line in table | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
/* Push in th/td tokens */ | ||
for (c = 0; c < trToken.meta.bounds.length - 1; c++) { | ||
range = [ trToken.meta.bounds[c] + 1, trToken.meta.bounds[c + 1] ]; | ||
text = state.src.slice.apply(state.src, range); | ||
if (text === '') { | ||
colspan = leftToken.attrGet('colspan'); | ||
leftToken.attrSet('colspan', colspan === null ? 2 : colspan + 1); | ||
continue; | ||
} | ||
if (options.rowspan && upTokens[c] && text.trim() === '^^') { | ||
rowspan = upTokens[c].attrGet('rowspan'); | ||
upTokens[c].attrSet('rowspan', rowspan === null ? 2 : rowspan + 1); | ||
continue; | ||
} | ||
tag = (trToken.meta.type === 0x00100) ? 'th' : 'td'; | ||
token = state.push('table_column_open', tag, 1); | ||
token.map = trToken.map; | ||
token.attrs = []; | ||
if (tableToken.meta.sep.aligns[c]) { | ||
token.attrs.push([ 'style', 'text-align:' + tableToken.meta.sep.aligns[c] ]); | ||
} | ||
if (tableToken.meta.sep.wraps[c]) { | ||
token.attrs.push([ 'class', 'extend' ]); | ||
} | ||
leftToken = upTokens[c] = token; | ||
/* Multiline. Join the text and feed into markdown-it blockParser. */ | ||
if (options.multiline && trToken.meta.multiline && trToken.meta.mbounds) { | ||
text = [ text.trimRight() ]; | ||
for (b = 1; b < trToken.meta.mbounds.length; b++) { | ||
/* Line with N bounds has cells indexed from 0 to N-2 */ | ||
if (c > trToken.meta.mbounds[b].length - 2) { continue; } | ||
range = [ trToken.meta.mbounds[b][c] + 1, trToken.meta.mbounds[b][c + 1] ]; | ||
text.push(state.src.slice.apply(state.src, range).trimRight()); | ||
} | ||
if (NFAstate === 0x10100 || !captionAtFirst) { | ||
caption(state, lineText, line, false); | ||
} else { | ||
line--; | ||
} | ||
break; | ||
case 0x01000: | ||
theadLines[1] = line; | ||
token = state.push('thead_close', 'thead', -1); | ||
break; | ||
case 0x00100: | ||
if (NFAstate !== 0x01100) { // the first line in thead | ||
token = state.push('thead_open', 'thead', 1); | ||
token.map = theadLines = [ line + 1, 0 ]; | ||
rowspanState = []; | ||
} | ||
rowInfo = tableRow(state, lineText, line, false, separatorInfo, 'th', rowspanState); | ||
line += rowInfo.extractedTextLinesCount - 1; | ||
break; | ||
case 0x00010: | ||
if (NFAstate !== 0x10011) { // the first line in tbody | ||
token = state.push('tbody_open', 'tbody', 1); | ||
token.map = tbodyLines = [ line + 1, 0 ]; | ||
rowspanState = []; | ||
} | ||
rowInfo = tableRow(state, lineText, line, false, separatorInfo, 'td', rowspanState); | ||
line += rowInfo.extractedTextLinesCount - 1; | ||
break; | ||
case 0x00001: | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
break; | ||
case 0x00000: | ||
line--; | ||
break; | ||
state.md.block.parse(text.join('\n'), state.md, state.env, state.tokens); | ||
} else { | ||
token = state.push('inline', '', 0); | ||
token.content = text.trim(); | ||
token.map = trToken.map; | ||
token.children = []; | ||
} | ||
token = state.push('table_column_close', tag, -1); | ||
} | ||
NFAstate = transitions[NFAstate][candidate] || 0x00000; | ||
/* Push in tr and thead/tbody closed tokens */ | ||
state.push('tr_close', 'tr', -1); | ||
if (trToken.meta.grp & 0x01) { | ||
tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody'; | ||
token = state.push('table_group_close', tag, -1); | ||
tgroupLines[1] = trToken.map[1]; | ||
} | ||
} | ||
if (tbodyLines && !tbodyLines[1]) { // Corner case: table without tbody or EOL | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
} | ||
tableLines[1] = line; | ||
tableLines[1] = Math.max( | ||
tgroupLines[1], | ||
tableToken.meta.sep.map[1], | ||
tableToken.meta.cap ? tableToken.meta.cap.map[1] : -1 | ||
); | ||
token = state.push('table_close', 'table', -1); | ||
state.line = line; | ||
state.line = tableLines[1]; | ||
return true; | ||
@@ -364,3 +416,3 @@ } | ||
},{}]},{},[1])(1) | ||
},{"./lib/dfa.js":1}]},{},[2])(2) | ||
}); |
@@ -1,1 +0,1 @@ | ||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).markdownitMultimdTable=e()}}(function(){return function e(t,n,r){function a(o,i){if(!n[o]){if(!t[o]){var l="function"==typeof require&&require;if(!i&&l)return l(o,!0);if(s)return s(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var c=n[o]={exports:{}};t[o][0].call(c.exports,function(e){return a(t[o][1][e]||e)},c,c.exports,e,t,n,r)}return n[o].exports}for(var s="function"==typeof require&&require,o=0;o<r.length;o++)a(r[o]);return a}({1:[function(e,t,n){"use strict";t.exports=function(e,t){function n(e,t){var n=e.bMarks[t]+e.blkIndent,r=e.eMarks[t];return e.src.slice(n,r)}function r(e){for(var t=[],n=e.length,r=0,a=!1,s=!1,o=0;o<n;o++)switch(e.charCodeAt(o)){case 92:a=!0;break;case 96:!s&&a||(s=!s),a=!1;break;case 124:s||a||(t.push(e.slice(r,o)),r=o+1),a=!1;break;default:a=!1}return t.push(e.slice(r)),t}function a(e,t,n,r){var a=t.match(/^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/);if(!a)return!1;if(r)return!0;var s,o={caption:null,label:null};return o.content=a[1],o.label=a[2]||a[1],(s=e.push("caption_open","caption",1)).map=[n,n+1],s.attrs=[["id",o.label.toLowerCase().replace(/\W+/g,"")]],(s=e.push("inline","",0)).content=o.content,s.map=[n,n+1],s.children=[],s=e.push("caption_close","caption",-1),o}function s(e,t,n,r){var a,s,o,i;(a=t.split(/\n/).length)>1?(o=new(s=e.md.block).State(t,e.md,e.env,e.tokens),s.tokenize(o,0,a)):((i=e.push("inline","",0)).content=t,i.map=[n,r],i.children=[])}function o(e,a,o,i,l,u,c){var p,f;if(p={colspans:null,columns:null,extractedTextLinesCount:1},1===(f=r(a.replace(/^\||([^\\])\|$/g,"$1"))).length&&!/^\||[^\\]\|$/.test(a))return!1;if(i)return!0;if(t.enableMultilineRows&&"\\"===a.slice(-1)){var h,d,b,g=Function.prototype.call.bind(String.prototype.trim);f=r(a.replace(/\\$/,"").replace(/^\||([^\\])\|$/g,"$1"));var m=/^\s*/.exec(f[0])[0],x=new RegExp("^"+m+"|\\s+$","g");f=f.map(g);do{if(d=r((h=n(e,o+p.extractedTextLinesCount)).replace(/\\$/,"").replace(/^\||([^\\])\|$/g,"$1")),b="\\"!==h.slice(-1),1===d.length&&!/^\||[^\\]\|$|\\$/.test(h))return!1;if(d.length!==f.length&&!b)return!1;for(var v=0;v<d.length;v++)f[v]=f[v]||"",f[v]+="\n"+d[v].replace(x,"");p.extractedTextLinesCount+=1}while(!b)}var k=RegExp.prototype.test.bind(/[^\n]/);p.columns=f.filter(k),p.colspans=function(e){for(var t=0,n=[],r=e.length-1;r>=0;r--)e[r]?(n.unshift(t+1),t=0):t++;return t>0&&n.unshift(t+1),n}(f.map(k));var w=e.push("tr_open","tr",1);w.map=[o,o+p.extractedTextLinesCount];for(var y=0,$=0;y<p.columns.length&&$<l.aligns.length;$+=p.colspans[y],y++)if(t.enableRowspan&&c&&c[y]&&/^\s*\^\^\s*$/.test(p.columns[y])){var _=c[y].attrs.find(function(e){return"rowspan"===e[0]});_||(_=["rowspan",1],c[y].attrs.push(_)),_[1]++}else(w=e.push(u+"_open",u,1)).map=[o,o+p.extractedTextLinesCount],w.attrs=[],c[y]={attrs:w.attrs},l.aligns[$]&&w.attrs.push(["style","text-align:"+l.aligns[$]]),l.wraps[$]&&w.attrs.push(["class","extend"]),p.colspans[y]>1&&w.attrs.push(["colspan",p.colspans[y]]),s(e,p.columns[y].trim(),o,o+p.extractedTextLinesCount),w=e.push(u+"_close",u,-1);return e.push("tr_close","tr",-1),p}function i(e,t,n,a){if(e.sCount[n]-e.blkIndent>=4)return!1;var s=r(t.replace(/^\||([^\\])\|$/g,"$1"));if(1===s.length&&!/^\||[^\\]\|$/.test(t))return!1;for(var o={aligns:[],wraps:[]},i=0;i<s.length;i++){var l=s[i].trim();if(!/^:?(-+|=+):?\+?$/.test(l))return!1;switch(o.wraps.push(43===l.charCodeAt(l.length-1)),o.wraps[i]&&(l=l.slice(0,-1)),((58===l.charCodeAt(0))<<4)+(58===l.charCodeAt(l.length-1))){case 0:o.aligns.push("");break;case 1:o.aligns.push("right");break;case 16:o.aligns.push("left");break;case 17:o.aligns.push("center")}}return a||o}t=t||{},e.block.ruler.at("table",function(e,t,r,s){var l,u,c,p,f,h={65536:function(e,t,n){return a(e,n,t,!0)},4096:function(e,t,n){return i(e,n,t)},256:function(e,t,n){return o(e,n,t,!0,null,"th")},16:function(e,t,n){return o(e,n,t,!0,null,"td")},1:function(e,t,n){return!n}},d={65792:{65536:256,256:4352},256:{256:4352},4352:{4096:65552,256:4352},65552:{65536:0,16:65553},65553:{65536:0,16:65553,1:65552}};if(t+2>r)return!1;var b,g,m,x,v=!1;for(l=65792,u=t;l&&u<r;u++){for(p=n(e,u).trim(),c=65536;c>0&&!(l&c&&h[c].call(this,e,u,p));c>>=4);switch(c){case 65536:65792===l&&(v=!0);break;case 4096:if(f=i(e,p,u),s)return!0;break;case 256:case 16:case 1:break;case 0:if(256&l)return!1}l=d[l][c]||0}if(!f)return!1;for(e.push("table_open","table",1).map=b=[t,0],l=65792,u=t;l&&u<r;u++){for(p=n(e,u).trim(),c=65536;c>0&&!(l&c&&h[c].call(this,e,u,p));c>>=4);switch(c){case 65536:65792!==l&&(m[1]=u,e.push("tbody_close","tbody",-1)),65792!==l&&v?u--:a(e,p,u,!1);break;case 4096:g[1]=u,e.push("thead_close","thead",-1);break;case 256:4352!==l&&(e.push("thead_open","thead",1).map=g=[u+1,0],x=[]),u+=o(e,p,u,!1,f,"th",x).extractedTextLinesCount-1;break;case 16:65553!==l&&(e.push("tbody_open","tbody",1).map=m=[u+1,0],x=[]),u+=o(e,p,u,!1,f,"td",x).extractedTextLinesCount-1;break;case 1:m[1]=u,e.push("tbody_close","tbody",-1);break;case 0:u--}l=d[l][c]||0}return m&&!m[1]&&(m[1]=u,e.push("tbody_close","tbody",-1)),b[1]=u,e.push("table_close","table",-1),e.line=u,!0},{alt:["paragraph","reference"]})}},{}]},{},[1])(1)}); | ||
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).markdownitMultimdTable=t()}}(function(){return function t(e,a,s){function n(r,o){if(!a[r]){if(!e[r]){var p="function"==typeof require&&require;if(!o&&p)return p(r,!0);if(i)return i(r,!0);var l=new Error("Cannot find module '"+r+"'");throw l.code="MODULE_NOT_FOUND",l}var _=a[r]={exports:{}};e[r][0].call(_.exports,function(t){return n(e[r][1][t]||t)},_,_.exports,t,e,a,s)}return a[r].exports}for(var i="function"==typeof require&&require,r=0;r<s.length;r++)n(s[r]);return n}({1:[function(t,e,a){"use strict";function s(){this.__highest_alphabet__=0,this.__match_alphabets__={},this.__initial_state__=0,this.__accept_states__={},this.__transitions__={},this.__actions__={}}s.prototype.set_highest_alphabet=function(t){this.__highest_alphabet__=t},s.prototype.set_match_alphabets=function(t){this.__match_alphabets__=t},s.prototype.set_initial_state=function(t){this.__initial_state__=t},s.prototype.set_accept_states=function(t){for(var e=0;e<t.length;e++)this.__accept_states__[t[e]]=!0},s.prototype.set_transitions=function(t){this.__transitions__=t},s.prototype.set_actions=function(t){this.__actions__=t},s.prototype.update_transition=function(t,e){this.__transitions__[t]=Object.assign(this.__transitions__[t]||Object(),e)},s.prototype.execute=function(t,e){var a,s,n;for(a=this.__initial_state__,s=t;a&&s<e;s++){for(n=this.__highest_alphabet__;n>0&&!(a&n&&this.__match_alphabets__[n].call(this,s,a,n));n>>=4);if(this.__actions__(s,a,n),0===n)break;a=this.__transitions__[a][n]||0}return!!this.__accept_states__[a]},e.exports=s},{}],2:[function(t,e,a){"use strict";var s=t("./lib/dfa.js");e.exports=function(t,e){function a(t,e){var a,s=t.bMarks[e],n=t.eMarks[e],i=[],r=!1,o=!1;for(a=s;a<n;a++)switch(t.src.charCodeAt(a)){case 92:r=!0;break;case 96:!o&&r||(o=!o),96===t.src.charCodeAt(a-1)&&(o=!1),r=!1;break;case 124:o||r||i.push(a),r=!1;break;default:r=!1}return 0===i.length?i:(i[0]>s&&i.unshift(s-1),i[i.length-1]<n-1&&i.push(n),i)}function n(t,e,a){var s=t.bMarks[a]+t.tShift[a],n=t.eMarks[a],i=t.src.slice(s,n).match(/^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/),r={};return!!i&&(!!e||(r.text=i[1],r.label=i[2]||i[1],r.label=r.label.toLowerCase().replace(/\W+/g,""),r))}function i(t,s,n){var i,r,o,p=a(t,n),l={};return!(p.length<2)&&(!!s||(l.bounds=p,e.multiline&&(i=t.bMarks[n]+t.tShift[n],r=t.eMarks[n]-1,l.multiline=92===t.src.charCodeAt(r),l.multiline&&(o=t.eMarks[n],t.eMarks[n]=t.skipSpacesBack(r,i),l.bounds=a(t,n),t.eMarks[n]=o)),l))}function r(t,e,s){var n,i,r=a(t,s),o={aligns:[],wraps:[]},p=/^:?(-+|=+):?\+?$/;if(t.sCount[s]-t.blkIndent>=4)return!1;if(0===r.length)return!1;for(n=0;n<r.length-1;n++){if(i=t.src.slice(r[n]+1,r[n+1]).trim(),!p.test(i))return!1;switch(o.wraps.push(43===i.charCodeAt(i.length-1)),(58===i.charCodeAt(0))<<4|58===i.charCodeAt(i.length-1-o.wraps[n])){case 0:o.aligns.push("");break;case 1:o.aligns.push("right");break;case 16:o.aligns.push("left");break;case 17:o.aligns.push("center")}}return!!e||o}function o(t,e,a){return t.bMarks[a]+t.tShift[a]===t.eMarks[a]}e=e||{},t.block.ruler.at("table",function(t,a,p,l){var _,c,u,h,m,f,b,d,g,k,w,y,v,x,M=new s,C=16,j=-1,A=[];if(a+2>p)return!1;if((c=new t.Token("table_open","table",1)).meta={sep:null,cap:null,tr:[]},M.set_highest_alphabet(65536),M.set_initial_state(65792),M.set_accept_states([65552,65553,0]),M.set_match_alphabets({65536:n.bind(this,t,!0),4096:r.bind(this,t,!0),256:i.bind(this,t,!0),16:i.bind(this,t,!0),1:o.bind(this,t,!0)}),M.set_transitions({65792:{65536:256,256:4352},256:{256:4352},4352:{4096:65552,256:4352},65552:{65536:0,16:65553},65553:{65536:0,16:65553,1:65552}}),e.headerless&&(M.set_initial_state(69888),M.update_transition(69888,{65536:4352,4096:65552,256:4352}),(u=new t.Token("table_fake_header_row","tr",1)).meta=Object()),M.set_actions(function(s,o,p){switch(p){case 65536:if(c.meta.cap)break;c.meta.cap=n(t,!1,s),c.meta.cap.map=[s,s+1],c.meta.cap.first=s===a;break;case 4096:c.meta.sep=r(t,!1,s),c.meta.sep.map=[s,s+1],u.meta.grp|=1,C=16;break;case 256:case 16:(u=new t.Token("table_row_open","tr",1)).map=[s,s+1],u.meta=i(t,!1,s),u.meta.type=p,u.meta.grp=C,C=0,c.meta.tr.push(u),e.multiline&&(u.meta.multiline&&j<0?j=c.meta.tr.length-1:!u.meta.multiline&&j>=0&&((_=c.meta.tr[j]).meta.mbounds=c.meta.tr.slice(j).map(function(t){return t.meta.bounds}),_.map[1]=u.map[1],c.meta.tr=c.meta.tr.slice(0,j+1),j=-1));break;case 1:u.meta.grp|=1,C=16}}),!1===M.execute(a,p))return!1;if(!c.meta.tr.length)return!1;if(l)return!0;for(c.meta.tr[c.meta.tr.length-1].meta.grp|=1,c.map=b=[a,0],c.block=!0,c.level=t.level++,t.tokens.push(c),c.meta.cap&&((_=t.push("caption_open","caption",1)).map=c.meta.cap.map,_.attrs=[["id",c.meta.cap.label]],(_=t.push("inline","",0)).content=c.meta.cap.text,_.map=c.meta.cap.map,_.children=[],_=t.push("caption_close","caption",-1)),y=0;y<c.meta.tr.length;y++){for(m=new t.Token("table_fake_tcol_open","",1),16&(u=c.meta.tr[y]).meta.grp&&(g=256===u.meta.type?"thead":"tbody",(_=t.push("table_group_open",g,1)).map=d=[u.map[0],0],A=[]),u.block=!0,u.level=t.level++,t.tokens.push(u),v=0;v<u.meta.bounds.length-1;v++)if(w=[u.meta.bounds[v]+1,u.meta.bounds[v+1]],""!==(k=t.src.slice.apply(t.src,w)))if(e.rowspan&&A[v]&&"^^"===k.trim())f=A[v].attrGet("rowspan"),A[v].attrSet("rowspan",null===f?2:f+1);else{if(g=256===u.meta.type?"th":"td",(_=t.push("table_column_open",g,1)).map=u.map,_.attrs=[],c.meta.sep.aligns[v]&&_.attrs.push(["style","text-align:"+c.meta.sep.aligns[v]]),c.meta.sep.wraps[v]&&_.attrs.push(["class","extend"]),m=A[v]=_,e.multiline&&u.meta.multiline&&u.meta.mbounds){for(k=[k.trimRight()],x=1;x<u.meta.mbounds.length;x++)v>u.meta.mbounds[x].length-2||(w=[u.meta.mbounds[x][v]+1,u.meta.mbounds[x][v+1]],k.push(t.src.slice.apply(t.src,w).trimRight()));t.md.block.parse(k.join("\n"),t.md,t.env,t.tokens)}else(_=t.push("inline","",0)).content=k.trim(),_.map=u.map,_.children=[];_=t.push("table_column_close",g,-1)}else h=m.attrGet("colspan"),m.attrSet("colspan",null===h?2:h+1);t.push("tr_close","tr",-1),1&u.meta.grp&&(g=256===u.meta.type?"thead":"tbody",_=t.push("table_group_close",g,-1),d[1]=u.map[1])}return b[1]=Math.max(d[1],c.meta.sep.map[1],c.meta.cap?c.meta.cap.map[1]:-1),_=t.push("table_close","table",-1),t.line=b[1],!0},{alt:["paragraph","reference"]})}},{"./lib/dfa.js":1}]},{},[2])(2)}); |
536
index.js
'use strict'; | ||
var DFA = require('./lib/dfa.js'); | ||
module.exports = function multimd_table_plugin(md, pluginOptions) { | ||
pluginOptions = pluginOptions || {}; | ||
module.exports = function multimd_table_plugin(md, options) { | ||
// TODO be consistent with markdown-it method | ||
options = options || {}; | ||
function getLine(state, line) { | ||
var pos = state.bMarks[line] + state.blkIndent, | ||
max = state.eMarks[line]; | ||
return state.src.slice(pos, max); | ||
} | ||
function scan_bound_indices(state, line) { | ||
var start = state.bMarks[line], /* no tShift to detect \n */ | ||
max = state.eMarks[line], | ||
bounds = [], pos, | ||
escape = false, code = false; | ||
function escapedSplit(str) { | ||
var result = [], | ||
max = str.length, | ||
lastPos = 0, | ||
escaped = false, | ||
backTicked = false; | ||
for (var pos = 0; pos < max; pos++) { | ||
switch (str.charCodeAt(pos)) { | ||
/* Scan for valid pipe character position */ | ||
for (pos = start; pos < max; pos++) { | ||
switch (state.src.charCodeAt(pos)) { | ||
case 0x5c /* \ */: | ||
escaped = true; | ||
break; | ||
escape = true; break; | ||
case 0x60 /* ` */: | ||
if (backTicked || !escaped) { | ||
/* make \` closes the code sequence, but not open it; | ||
the reason is that `\` is correct code block */ | ||
backTicked = !backTicked; | ||
} | ||
escaped = false; | ||
break; | ||
/* make \` closes the code sequence, but not open it; | ||
the reason is that `\` is correct code block */ | ||
if (code || !escape) { code = !code; } | ||
if (state.src.charCodeAt(pos - 1) === 0x60) { code = false; } | ||
escape = false; break; | ||
case 0x7c /* | */: | ||
if (!backTicked && !escaped) { | ||
result.push(str.slice(lastPos, pos)); | ||
lastPos = pos + 1; | ||
} | ||
escaped = false; | ||
break; | ||
if (!code && !escape) { bounds.push(pos); } | ||
escape = false; break; | ||
default: | ||
escaped = false; | ||
break; | ||
escape = false; break; | ||
} | ||
} | ||
if (bounds.length === 0) return bounds; | ||
result.push(str.slice(lastPos)); | ||
/* Pad in newline characters on last and this line */ | ||
if (bounds[0] > start) { bounds.unshift(start - 1); } | ||
if (bounds[bounds.length - 1] < max - 1) { bounds.push(max); } | ||
return result; | ||
return bounds; | ||
} | ||
function countColspan(columns) { | ||
var emptyCount = 0, | ||
colspans = []; | ||
function table_caption(state, silent, line) { | ||
var start = state.bMarks[line] + state.tShift[line], | ||
max = state.eMarks[line], | ||
capRE = /^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/, | ||
matches = state.src.slice(start, max).match(capRE), | ||
meta = {}; | ||
for (var i = columns.length - 1; i >= 0; i--) { | ||
if (columns[i]) { | ||
colspans.unshift(emptyCount + 1); | ||
emptyCount = 0; | ||
} else { | ||
emptyCount++; | ||
} | ||
} | ||
if (emptyCount > 0) { | ||
colspans.unshift(emptyCount + 1); | ||
} | ||
return colspans; | ||
} | ||
function caption(state, lineText, lineNum, silent) { | ||
var result = lineText.match(/^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/); | ||
if (!result) { return false; } | ||
if (!matches) { return false; } | ||
if (silent) { return true; } | ||
// TODO eliminate capRE by simple checking | ||
var captionInfo = { caption: null, label: null }; | ||
captionInfo.content = result[1]; | ||
captionInfo.label = result[2] || result[1]; | ||
meta.text = matches[1]; | ||
meta.label = matches[2] || matches[1]; | ||
meta.label = meta.label.toLowerCase().replace(/\W+/g, ''); | ||
var token; | ||
token = state.push('caption_open', 'caption', 1); | ||
token.map = [ lineNum, lineNum + 1 ]; | ||
token.attrs = [ [ 'id', captionInfo.label.toLowerCase().replace(/\W+/g, '') ] ]; | ||
token = state.push('inline', '', 0); | ||
token.content = captionInfo.content; | ||
token.map = [ lineNum, lineNum + 1 ]; | ||
token.children = []; | ||
token = state.push('caption_close', 'caption', -1); | ||
return captionInfo; | ||
return meta; | ||
} | ||
function appendRowToken(state, content, startLine, endLine) { | ||
var linesCount, blockParser, tmpState, token; | ||
linesCount = content.split(/\n/).length; | ||
function table_row(state, silent, line) { | ||
var bounds = scan_bound_indices(state, line), | ||
meta = {}, start, pos, oldMax; | ||
if (linesCount > 1) { | ||
// Multiline content => subparsing as a block to support lists | ||
blockParser = state.md.block; | ||
tmpState = new blockParser.State(content, state.md, state.env, state.tokens); | ||
blockParser.tokenize(tmpState, 0, linesCount); // appends to state.tokens | ||
} else { | ||
token = state.push('inline', '', 0); | ||
token.content = content; | ||
token.map = [ startLine, endLine ]; | ||
token.children = []; | ||
} | ||
} | ||
function tableRow(state, lineText, lineNum, silent, separatorInfo, rowType, rowspanState) { | ||
var rowInfo, columns; | ||
rowInfo = { colspans: null, columns: null, extractedTextLinesCount: 1 }; | ||
columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1')); | ||
// lineText does not contain valid pipe character | ||
if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; } | ||
if (bounds.length < 2) { return false; } | ||
if (silent) { return true; } | ||
// Multiline feature | ||
if (pluginOptions.enableMultilineRows && lineText.slice(-1) === '\\') { | ||
var lineTextNext, columnsNext, EndOfMultilines; | ||
var trimItself = Function.prototype.call.bind(String.prototype.trim); // equal to (x => x.trim()) | ||
columns = escapedSplit(lineText.replace(/\\$/, '').replace(/^\||([^\\])\|$/g, '$1')); | ||
var initialIndent = /^\s*/.exec(columns[0])[0]; | ||
var trimRegex = new RegExp('^' + initialIndent + '|\\s+$', 'g'); | ||
columns = columns.map(trimItself); | ||
do { | ||
lineTextNext = getLine(state, lineNum + rowInfo.extractedTextLinesCount); | ||
columnsNext = escapedSplit(lineTextNext.replace(/\\$/, '').replace(/^\||([^\\])\|$/g, '$1')); | ||
EndOfMultilines = lineTextNext.slice(-1) !== '\\'; | ||
meta.bounds = bounds; | ||
if (columnsNext.length === 1 && !/^\||[^\\]\|$|\\$/.test(lineTextNext)) { return false; } | ||
if (columnsNext.length !== columns.length && !EndOfMultilines) { return false; } | ||
for (var j = 0; j < columnsNext.length; j++) { | ||
columns[j] = columns[j] || ''; | ||
columns[j] += '\n' + columnsNext[j].replace(trimRegex, ''); | ||
} | ||
rowInfo.extractedTextLinesCount += 1; | ||
} while (!EndOfMultilines); | ||
} | ||
// Fill in HTML <tr> elements | ||
var isValidColumn = RegExp.prototype.test.bind(/[^\n]/); // equal to (s => /[^\n]/.test(s)) | ||
rowInfo.columns = columns.filter(isValidColumn); | ||
rowInfo.colspans = countColspan(columns.map(isValidColumn)); | ||
var token = state.push('tr_open', 'tr', 1); | ||
token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ]; | ||
for (var i = 0, col = 0; | ||
i < rowInfo.columns.length && col < separatorInfo.aligns.length; | ||
col += rowInfo.colspans[i], i++) { | ||
if (pluginOptions.enableRowspan && | ||
rowspanState && rowspanState[i] && | ||
/^\s*\^\^\s*$/.test(rowInfo.columns[i])) { | ||
var rowspanAttr = rowspanState[i].attrs.find(function (attr) { | ||
return attr[0] === 'rowspan'; | ||
}); | ||
if (!rowspanAttr) { | ||
rowspanAttr = [ 'rowspan', 1 ]; | ||
rowspanState[i].attrs.push(rowspanAttr); | ||
} | ||
rowspanAttr[1]++; | ||
continue; | ||
/* Multiline. Scan boundaries again since it's very complicated */ | ||
if (options.multiline) { | ||
start = state.bMarks[line] + state.tShift[line]; | ||
pos = state.eMarks[line] - 1; /* where backslash should be */ | ||
meta.multiline = (state.src.charCodeAt(pos) === 0x5C/* \ */); | ||
if (meta.multiline) { | ||
oldMax = state.eMarks[line]; | ||
state.eMarks[line] = state.skipSpacesBack(pos, start); | ||
meta.bounds = scan_bound_indices(state, line); | ||
state.eMarks[line] = oldMax; | ||
} | ||
token = state.push(rowType + '_open', rowType, 1); | ||
token.map = [ lineNum, lineNum + rowInfo.extractedTextLinesCount ]; | ||
token.attrs = []; | ||
rowspanState[i] = { | ||
attrs: token.attrs | ||
}; | ||
if (separatorInfo.aligns[col]) { | ||
token.attrs.push([ 'style', 'text-align:' + separatorInfo.aligns[col] ]); | ||
} | ||
if (separatorInfo.wraps[col]) { | ||
token.attrs.push([ 'class', 'extend' ]); | ||
} | ||
if (rowInfo.colspans[i] > 1) { | ||
token.attrs.push([ 'colspan', rowInfo.colspans[i] ]); | ||
} | ||
appendRowToken(state, rowInfo.columns[i].trim(), lineNum, lineNum + rowInfo.extractedTextLinesCount); | ||
token = state.push(rowType + '_close', rowType, -1); | ||
} | ||
state.push('tr_close', 'tr', -1); | ||
return rowInfo; | ||
return meta; | ||
} | ||
function separator(state, lineText, lineNum, silent) { | ||
// lineText have code indentation | ||
if (state.sCount[lineNum] - state.blkIndent >= 4) { return false; } | ||
function table_separator(state, silent, line) { | ||
var bounds = scan_bound_indices(state, line), | ||
meta = { aligns: [], wraps: [] }, | ||
sepRE = /^:?(-+|=+):?\+?$/, | ||
c, text, align; | ||
// lineText does not contain valid pipe character | ||
var columns = escapedSplit(lineText.replace(/^\||([^\\])\|$/g, '$1')); | ||
if (columns.length === 1 && !/^\||[^\\]\|$/.test(lineText)) { return false; } | ||
/* Only separator needs to check indents */ | ||
if (state.sCount[line] - state.blkIndent >= 4) { return false; } | ||
if (bounds.length === 0) { return false; } | ||
var separatorInfo = { aligns: [], wraps: [] }; | ||
for (c = 0; c < bounds.length - 1; c++) { | ||
text = state.src.slice(bounds[c] + 1, bounds[c + 1]).trim(); | ||
if (!sepRE.test(text)) { return false; } | ||
for (var i = 0; i < columns.length; i++) { | ||
var t = columns[i].trim(); | ||
if (!/^:?(-+|=+):?\+?$/.test(t)) { return false; } | ||
separatorInfo.wraps.push(t.charCodeAt(t.length - 1) === 0x2B/* + */); | ||
if (separatorInfo.wraps[i]) { | ||
t = t.slice(0, -1); | ||
meta.wraps.push(text.charCodeAt(text.length - 1) === 0x2B/* + */); | ||
align = ((text.charCodeAt(0) === 0x3A/* : */) << 4) | | ||
(text.charCodeAt(text.length - 1 - meta.wraps[c]) === 0x3A); | ||
switch (align) { | ||
case 0x00: meta.aligns.push(''); break; | ||
case 0x01: meta.aligns.push('right'); break; | ||
case 0x10: meta.aligns.push('left'); break; | ||
case 0x11: meta.aligns.push('center'); break; | ||
} | ||
switch (((t.charCodeAt(0) === 0x3A /* : */) << 4) + | ||
(t.charCodeAt(t.length - 1) === 0x3A /* : */)) { | ||
case 0x00: separatorInfo.aligns.push(''); break; | ||
case 0x01: separatorInfo.aligns.push('right'); break; | ||
case 0x10: separatorInfo.aligns.push('left'); break; | ||
case 0x11: separatorInfo.aligns.push('center'); break; | ||
} | ||
} | ||
if (silent) { return true; } | ||
return meta; | ||
} | ||
return silent || separatorInfo; | ||
function table_empty(state, silent, line) { | ||
var start = state.bMarks[line] + state.tShift[line], | ||
max = state.eMarks[line]; | ||
return start === max; | ||
} | ||
@@ -227,18 +120,39 @@ | ||
/* Regex pseudo code for table: | ||
* caption? header+ separator (data+ empty)* data+ caption? | ||
* caption? header+ separator (data+ empty)* data+ caption? | ||
* | ||
* We use NFA with precedences to emulate this plugin. | ||
* Noted that separator should have higher precedence than header or data. | ||
* We use DFA to emulate this plugin. Types with lower precedence are | ||
* set-minus from all the formers. Noted that separator should have higher | ||
* precedence than header or data. | ||
* | state | caption separator header data empty | --> lower precedence | ||
* | 0x10100 | 1 0 1 0 0 | | ||
*/ | ||
var tableDFA = new DFA(), | ||
grp = 0x10, mtr = -1, | ||
token, tableToken, trToken, | ||
colspan, leftToken, | ||
rowspan, upTokens = [], | ||
tableLines, tgroupLines, | ||
tag, text, range, r, c, b; | ||
var match = { | ||
0x10000: function (s, l, lt) { return caption(s, lt, l, true); }, | ||
0x01000: function (s, l, lt) { return separator(s, lt, l); }, | ||
0x00100: function (s, l, lt) { return tableRow(s, lt, l, true, null, 'th'); }, | ||
0x00010: function (s, l, lt) { return tableRow(s, lt, l, true, null, 'td'); }, | ||
0x00001: function (s, l, lt) { return !lt; } | ||
}; | ||
var transitions = { | ||
if (startLine + 2 > endLine) { return false; } | ||
/** | ||
* First pass: validate and collect info into table token. IR is stored in | ||
* markdown-it `token.meta` to be pushed later. table/tr open tokens are | ||
* generated here. | ||
*/ | ||
tableToken = new state.Token('table_open', 'table', 1); | ||
tableToken.meta = { sep: null, cap: null, tr: [] }; | ||
tableDFA.set_highest_alphabet(0x10000); | ||
tableDFA.set_initial_state(0x10100); | ||
tableDFA.set_accept_states([ 0x10010, 0x10011, 0x00000 ]); | ||
tableDFA.set_match_alphabets({ | ||
0x10000: table_caption.bind(this, state, true), | ||
0x01000: table_separator.bind(this, state, true), | ||
0x00100: table_row.bind(this, state, true), | ||
0x00010: table_row.bind(this, state, true), | ||
0x00001: table_empty.bind(this, state, true) | ||
}); | ||
tableDFA.set_transitions({ | ||
0x10100: { 0x10000: 0x00100, 0x00100: 0x01100 }, | ||
@@ -249,107 +163,171 @@ 0x00100: { 0x00100: 0x01100 }, | ||
0x10011: { 0x10000: 0x00000, 0x00010: 0x10011, 0x00001: 0x10010 } | ||
}; | ||
/* Check validity; Gather separator informations */ | ||
if (startLine + 2 > endLine) { return false; } | ||
var NFAstate, line, candidate, rowInfo, lineText, separatorInfo; | ||
var captionAtFirst = false; | ||
for (NFAstate = 0x10100, line = startLine; NFAstate && line < endLine; line++) { | ||
lineText = getLine(state, line).trim(); | ||
for (candidate = 0x10000; candidate > 0; candidate >>= 4) { | ||
if (NFAstate & candidate && match[candidate].call(this, state, line, lineText)) { break; } | ||
} | ||
switch (candidate) { | ||
}); | ||
if (options.headerless) { | ||
tableDFA.set_initial_state(0x11100); | ||
tableDFA.update_transition(0x11100, | ||
{ 0x10000: 0x01100, 0x01000: 0x10010, 0x00100: 0x01100 } | ||
); | ||
trToken = new state.Token('table_fake_header_row', 'tr', 1); | ||
trToken.meta = Object(); // avoid trToken.meta.grp throws exception | ||
} | ||
/* Don't mix up DFA `_state` and markdown-it `state` */ | ||
tableDFA.set_actions(function (_line, _state, _type) { | ||
switch (_type) { | ||
case 0x10000: | ||
if (NFAstate === 0x10100) { captionAtFirst = true; } | ||
if (tableToken.meta.cap) { break; } | ||
tableToken.meta.cap = table_caption(state, false, _line); | ||
tableToken.meta.cap.map = [ _line, _line + 1 ]; | ||
tableToken.meta.cap.first = (_line === startLine); | ||
break; | ||
case 0x01000: | ||
separatorInfo = separator(state, lineText, line); | ||
if (silent) { return true; } | ||
tableToken.meta.sep = table_separator(state, false, _line); | ||
tableToken.meta.sep.map = [ _line, _line + 1 ]; | ||
trToken.meta.grp |= 0x01; // previously assigned at case 0x00110 | ||
grp = 0x10; | ||
break; | ||
case 0x00100: | ||
case 0x00010: | ||
trToken = new state.Token('table_row_open', 'tr', 1); | ||
trToken.map = [ _line, _line + 1 ]; | ||
trToken.meta = table_row(state, false, _line); | ||
trToken.meta.type = _type; | ||
trToken.meta.grp = grp; | ||
grp = 0x00; | ||
tableToken.meta.tr.push(trToken); | ||
/* Multiline. Merge trTokens as an entire multiline trToken */ | ||
if (options.multiline) { | ||
if (trToken.meta.multiline && mtr < 0) { | ||
/* Start line of multiline row. mark this trToken */ | ||
mtr = tableToken.meta.tr.length - 1; | ||
} else if (!trToken.meta.multiline && mtr >= 0) { | ||
/* End line of multiline row. merge forward until the marked trToken */ | ||
token = tableToken.meta.tr[mtr]; | ||
token.meta.mbounds = tableToken.meta.tr | ||
.slice(mtr).map(function (tk) { return tk.meta.bounds; }); | ||
token.map[1] = trToken.map[1]; | ||
tableToken.meta.tr = tableToken.meta.tr.slice(0, mtr + 1); | ||
mtr = -1; | ||
} | ||
} | ||
break; | ||
case 0x00001: | ||
trToken.meta.grp |= 0x01; | ||
grp = 0x10; | ||
break; | ||
case 0x00000: | ||
if (NFAstate & 0x00100) { return false; } // separator not reached | ||
} | ||
}); | ||
NFAstate = transitions[NFAstate][candidate] || 0x00000; | ||
} | ||
if (tableDFA.execute(startLine, endLine) === false) { return false; } | ||
// if (!tableToken.meta.sep) { return false; } // always evaluated true | ||
if (!tableToken.meta.tr.length) { return false; } // false under headerless corner case | ||
if (silent) { return true; } | ||
if (!separatorInfo) { return false; } | ||
/* Last data row cannot be detected. not stored to trToken outside? */ | ||
tableToken.meta.tr[tableToken.meta.tr.length - 1].meta.grp |= 0x01; | ||
/* Generate table HTML */ | ||
var token, tableLines, theadLines, tbodyLines; | ||
token = state.push('table_open', 'table', 1); | ||
token.map = tableLines = [ startLine, 0 ]; | ||
/** | ||
* Second pass: actually push the tokens into `state.tokens`. | ||
* thead/tbody/th/td open tokens and all closed tokens are generated here; | ||
* thead/tbody are generally called tgroup; td/th are generally called tcol. | ||
*/ | ||
tableToken.map = tableLines = [ startLine, 0 ]; | ||
tableToken.block = true; | ||
tableToken.level = state.level++; | ||
state.tokens.push(tableToken); | ||
var rowspanState; | ||
for (NFAstate = 0x10100, line = startLine; NFAstate && line < endLine; line++) { | ||
lineText = getLine(state, line).trim(); | ||
if (tableToken.meta.cap) { | ||
token = state.push('caption_open', 'caption', 1); | ||
token.map = tableToken.meta.cap.map; | ||
token.attrs = [ [ 'id', tableToken.meta.cap.label ] ]; | ||
for (candidate = 0x10000; candidate > 0; candidate >>= 4) { | ||
if (NFAstate & candidate && match[candidate].call(this, state, line, lineText)) { break; } | ||
token = state.push('inline', '', 0); | ||
token.content = tableToken.meta.cap.text; | ||
token.map = tableToken.meta.cap.map; | ||
token.children = []; | ||
token = state.push('caption_close', 'caption', -1); | ||
} | ||
for (r = 0; r < tableToken.meta.tr.length; r++) { | ||
leftToken = new state.Token('table_fake_tcol_open', '', 1); | ||
/* Push in thead/tbody and tr open tokens */ | ||
trToken = tableToken.meta.tr[r]; | ||
// console.log(trToken.meta); // for test | ||
if (trToken.meta.grp & 0x10) { | ||
tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody'; | ||
token = state.push('table_group_open', tag, 1); | ||
token.map = tgroupLines = [ trToken.map[0], 0 ]; // array ref | ||
upTokens = []; | ||
} | ||
trToken.block = true; | ||
trToken.level = state.level++; | ||
state.tokens.push(trToken); | ||
switch (candidate) { | ||
case 0x10000: | ||
if (NFAstate !== 0x10100) { // the last line in table | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
/* Push in th/td tokens */ | ||
for (c = 0; c < trToken.meta.bounds.length - 1; c++) { | ||
range = [ trToken.meta.bounds[c] + 1, trToken.meta.bounds[c + 1] ]; | ||
text = state.src.slice.apply(state.src, range); | ||
if (text === '') { | ||
colspan = leftToken.attrGet('colspan'); | ||
leftToken.attrSet('colspan', colspan === null ? 2 : colspan + 1); | ||
continue; | ||
} | ||
if (options.rowspan && upTokens[c] && text.trim() === '^^') { | ||
rowspan = upTokens[c].attrGet('rowspan'); | ||
upTokens[c].attrSet('rowspan', rowspan === null ? 2 : rowspan + 1); | ||
continue; | ||
} | ||
tag = (trToken.meta.type === 0x00100) ? 'th' : 'td'; | ||
token = state.push('table_column_open', tag, 1); | ||
token.map = trToken.map; | ||
token.attrs = []; | ||
if (tableToken.meta.sep.aligns[c]) { | ||
token.attrs.push([ 'style', 'text-align:' + tableToken.meta.sep.aligns[c] ]); | ||
} | ||
if (tableToken.meta.sep.wraps[c]) { | ||
token.attrs.push([ 'class', 'extend' ]); | ||
} | ||
leftToken = upTokens[c] = token; | ||
/* Multiline. Join the text and feed into markdown-it blockParser. */ | ||
if (options.multiline && trToken.meta.multiline && trToken.meta.mbounds) { | ||
text = [ text.trimRight() ]; | ||
for (b = 1; b < trToken.meta.mbounds.length; b++) { | ||
/* Line with N bounds has cells indexed from 0 to N-2 */ | ||
if (c > trToken.meta.mbounds[b].length - 2) { continue; } | ||
range = [ trToken.meta.mbounds[b][c] + 1, trToken.meta.mbounds[b][c + 1] ]; | ||
text.push(state.src.slice.apply(state.src, range).trimRight()); | ||
} | ||
if (NFAstate === 0x10100 || !captionAtFirst) { | ||
caption(state, lineText, line, false); | ||
} else { | ||
line--; | ||
} | ||
break; | ||
case 0x01000: | ||
theadLines[1] = line; | ||
token = state.push('thead_close', 'thead', -1); | ||
break; | ||
case 0x00100: | ||
if (NFAstate !== 0x01100) { // the first line in thead | ||
token = state.push('thead_open', 'thead', 1); | ||
token.map = theadLines = [ line + 1, 0 ]; | ||
rowspanState = []; | ||
} | ||
rowInfo = tableRow(state, lineText, line, false, separatorInfo, 'th', rowspanState); | ||
line += rowInfo.extractedTextLinesCount - 1; | ||
break; | ||
case 0x00010: | ||
if (NFAstate !== 0x10011) { // the first line in tbody | ||
token = state.push('tbody_open', 'tbody', 1); | ||
token.map = tbodyLines = [ line + 1, 0 ]; | ||
rowspanState = []; | ||
} | ||
rowInfo = tableRow(state, lineText, line, false, separatorInfo, 'td', rowspanState); | ||
line += rowInfo.extractedTextLinesCount - 1; | ||
break; | ||
case 0x00001: | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
break; | ||
case 0x00000: | ||
line--; | ||
break; | ||
state.md.block.parse(text.join('\n'), state.md, state.env, state.tokens); | ||
} else { | ||
token = state.push('inline', '', 0); | ||
token.content = text.trim(); | ||
token.map = trToken.map; | ||
token.children = []; | ||
} | ||
token = state.push('table_column_close', tag, -1); | ||
} | ||
NFAstate = transitions[NFAstate][candidate] || 0x00000; | ||
/* Push in tr and thead/tbody closed tokens */ | ||
state.push('tr_close', 'tr', -1); | ||
if (trToken.meta.grp & 0x01) { | ||
tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody'; | ||
token = state.push('table_group_close', tag, -1); | ||
tgroupLines[1] = trToken.map[1]; | ||
} | ||
} | ||
if (tbodyLines && !tbodyLines[1]) { // Corner case: table without tbody or EOL | ||
tbodyLines[1] = line; | ||
token = state.push('tbody_close', 'tbody', -1); | ||
} | ||
tableLines[1] = line; | ||
tableLines[1] = Math.max( | ||
tgroupLines[1], | ||
tableToken.meta.sep.map[1], | ||
tableToken.meta.cap ? tableToken.meta.cap.map[1] : -1 | ||
); | ||
token = state.push('table_close', 'table', -1); | ||
state.line = line; | ||
state.line = tableLines[1]; | ||
return true; | ||
@@ -356,0 +334,0 @@ } |
{ | ||
"name": "markdown-it-multimd-table", | ||
"version": "3.2.3", | ||
"version": "4.0.0", | ||
"description": "Multimarkdown table syntax plugin for markdown-it markdown parser", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
271
README.md
@@ -8,20 +8,35 @@ [![NPM version](https://img.shields.io/npm/v/markdown-it-multimd-table.svg?style=flat)](https://www.npmjs.org/package/markdown-it-multimd-table) | ||
## Intro | ||
In general Markdown syntax, we have to write raw HTML tags when `colspan` attribute is needed. Luckily, I found that [MultiMarkdown](https://fletcher.github.io/MultiMarkdown-6/) had defined complete and clear rules for advanced Markdown table syntax, and compatible to general Markdown table syntax. | ||
When writing table in Markdown syntax, we have to fallback to write raw HTML tags, if we just need some advanced attribute like `colspan`. | ||
[MultiMarkdown](https://fletcher.github.io/MultiMarkdown-6/) is an extended Markdown spec covering fancy features. | ||
It has defined some complete and clear rules for advanced Markdown table syntax, and aims to be compatible to basic table syntax as possible. | ||
So I extend the table parser in markdown-it to support MultiMarkdown table syntax. For now, the following features are provided: | ||
[markdown-it](https://markdown-it.github.io/) is a powerful and widely-used Markdown compiler, in native it supports basic table syntax only. | ||
It allows plugins to expand it capability, and this plugin replaced the original table parser in markdown-it to support MultiMarkdown table syntax. | ||
For now, these extended features are provided: | ||
- Cells spanning multiple columns | ||
- Cells spanning multiple rows (optional) | ||
- Grouped table headers | ||
- Grouped table rows | ||
- Table captions | ||
- Lists in table cell (optional) | ||
- Line breaks in table cells (optional) | ||
- Grouped table header rows or data rows | ||
- Table caption above or below the table | ||
- Blocked elements (lists, codes, paragraphs...) in table (optional) | ||
- Table header not required (optional) | ||
Noted that the plugin might behave differently from MultiMarkdown in some edge cases; since the plugin aims just to follow the rules in [MultiMarkdown User's Guide](http://fletcher.github.io/MultiMarkdown-5/tables). | ||
Noted: the plugin is not a re-written of MultiMarkdown to deploy on markdown-it, it will generate HTML different from MultiMarkdown official compiler in some corner cases. | ||
This plugin try to follow the rule defined in [MultiMarkdown User's Guide](http://fletcher.github.io/MultiMarkdown-5/tables) as possible. | ||
If some case is reasonable but behaves strangely, please pose an issue for that. | ||
## Usage | ||
```javascript | ||
// defaults | ||
var md = require('markdown-it')() | ||
.use(require('markdown-it-multimd-table')); | ||
// full options list (same to defaults) | ||
var md = require('markdown-it')() | ||
.use(require('markdown-it-multimd-table'), { | ||
multiline: false, | ||
rowspan: false, | ||
headerless: false, | ||
}); | ||
md.render(/*...*/) | ||
@@ -63,8 +78,8 @@ ``` | ||
<th></th> | ||
<th style="text-align:center" colspan="2">Grouping</th> | ||
<th align="center" colspan="2">Grouping</th> | ||
</tr> | ||
<tr> | ||
<th>First Header</th> | ||
<th style="text-align:center">Second Header</th> | ||
<th style="text-align:right">Third Header</th> | ||
<th align="center">Second Header</th> | ||
<th align="right">Third Header</th> | ||
</tr> | ||
@@ -75,8 +90,8 @@ </thead> | ||
<td>Content</td> | ||
<td style="text-align:center" colspan="2"><em>Long Cell</em></td> | ||
<td align="center" colspan="2"><em>Long Cell</em></td> | ||
</tr> | ||
<tr> | ||
<td>Content</td> | ||
<td style="text-align:center"><strong>Cell</strong></td> | ||
<td style="text-align:right">Cell</td> | ||
<td align="center"><strong>Cell</strong></td> | ||
<td align="right">Cell</td> | ||
</tr> | ||
@@ -87,8 +102,8 @@ </tbody> | ||
<td>New section</td> | ||
<td style="text-align:center">More</td> | ||
<td style="text-align:right">Data</td> | ||
<td align="center">More</td> | ||
<td align="right">Data</td> | ||
</tr> | ||
<tr> | ||
<td>And more</td> | ||
<td style="text-align:center" colspan="2">With an escaped '|'</td> | ||
<td align="center" colspan="2">With an escaped '|'</td> | ||
</tr> | ||
@@ -99,17 +114,24 @@ </tbody> | ||
### Multiple lines of row (optional) | ||
Noted that GitHub filters out `style` property, so the example displays with | ||
the obsolete `align` property. But in actual this plugin outputs `style` | ||
property with `text-align` CSS attribute. | ||
Put backslashes at end to make the table rows parsed as multiple lines. | ||
### Multiline (optional) | ||
A backslash at end to join cell contents with the following lines.<br> | ||
This feature is contributed by [Lucas-C](https://github.com/Lucas-C). | ||
```markdown | ||
First header | Second header | ||
-------------|--------------- | ||
List: | More \ | ||
- over | data \ | ||
- several | \ | ||
- lines | | ||
| Markdown | Rendered HTML | | ||
|--------------|---------------| | ||
| *Italic* | *Italic* | \ | ||
| | | | ||
| - Item 1 | - Item 1 | \ | ||
| - Item 2 | - Item 2 | | ||
| ```python | ```python \ | ||
| .1 + .2 | .1 + .2 \ | ||
| ``` | ``` | | ||
``` | ||
would be parsed as | ||
If this option is enabled, code above would be parsed as: | ||
@@ -119,4 +141,4 @@ <table> | ||
<tr> | ||
<th>First header</th> | ||
<th>Second header</th> | ||
<th>Markdown</th> | ||
<th>Rendered HTML</th> | ||
</tr> | ||
@@ -127,13 +149,31 @@ </thead> | ||
<td> | ||
<p>List:</p> | ||
<pre><code>*Italic* | ||
</code></pre> | ||
</td> | ||
<td> | ||
<p><em>Italic</em></p> | ||
</td> | ||
</tr> | ||
<tr> | ||
<td> | ||
<pre><code>- Item 1 | ||
- Item 2</code></pre> | ||
</td> | ||
<td> | ||
<ul> | ||
<li>over</li> | ||
<li>several</li> | ||
<li>lines</li> | ||
<li>Item 1</li> | ||
<li>Item 2</li> | ||
</ul> | ||
</td> | ||
</tr> | ||
<tr> | ||
<td> | ||
<p>More | ||
data</p> | ||
<pre><code>```python | ||
.1 + .2 | ||
```</code></pre> | ||
</td> | ||
<td> | ||
<pre><code class="language-python">.1 + .2 | ||
</code></pre> | ||
</td> | ||
</tr> | ||
@@ -143,29 +183,29 @@ </tbody> | ||
And here's how you enable the feature. | ||
```javascript | ||
var md = require('markdown-it')() | ||
.use(require('markdown-it-multimd-table'), {enableMultilineRows: true}); | ||
``` | ||
### Rowspan (optional) | ||
To create cells with a rowspan mark the cells to merge up with `^^`. | ||
`^^` in a cell indicates it should be merged with the cell above.<br> | ||
This feature is contributed by [pmccloghrylaing](https://github.com/pmccloghrylaing). | ||
```markdown | ||
First header | Second header | ||
-------------|--------------- | ||
Merged | Cell 1 | ||
^^ | Cell 2 | ||
^^ | Cell 3 | ||
Stage | Direct Products | ATP Yields | ||
----: | --------------: | ---------: | ||
Glycolysis | 2 ATP || | ||
^^ | 2 NADH | 3--5 ATP | | ||
Pyruvaye oxidation | 2 NADH | 5 ATP | | ||
Citric acid cycle | 2 ATP || | ||
^^ | 6 NADH | 15 ATP | | ||
^^ | 2 FADH2 | 3 ATP | | ||
**30--32** ATP ||| | ||
[Net ATP yields per hexose] | ||
``` | ||
would be parsed as | ||
If this option is enabled, code above would be parsed as: | ||
<table> | ||
<caption id="netatpyieldsperhexose">Net ATP yields per hexose</caption> | ||
<thead> | ||
<tr> | ||
<th>First header</th> | ||
<th>Second header</th> | ||
<th align="right">Stage</th> | ||
<th align="right">Direct Products</th> | ||
<th align="right">ATP Yields</th> | ||
</tr> | ||
@@ -175,22 +215,137 @@ </thead> | ||
<tr> | ||
<td rowspan="3">Merged</td> | ||
<td>Cell 1</td> | ||
<td align="right" rowspan="2">Glycolysis</td> | ||
<td align="right" colspan="2">2 ATP</td> | ||
</tr> | ||
<tr> | ||
<td>Cell 2</td> | ||
<td align="right">2 NADH</td> | ||
<td align="right">3–5 ATP</td> | ||
</tr> | ||
<tr> | ||
<td>Cell 3</td> | ||
<td align="right">Pyruvaye oxidation</td> | ||
<td align="right">2 NADH</td> | ||
<td align="right">5 ATP</td> | ||
</tr> | ||
<tr> | ||
<td align="right" rowspan="3">Citric acid cycle</td> | ||
<td align="right" colspan="2">2 ATP</td> | ||
</tr> | ||
<tr> | ||
<td align="right">6 NADH</td> | ||
<td align="right">15 ATP</td> | ||
</tr> | ||
<tr> | ||
<td align="right">2 FADH2</td> | ||
<td align="right">3 ATP</td> | ||
</tr> | ||
<tr> | ||
<td align="right" colspan="3"><strong>30–32</strong> ATP</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
And here's how you enable the feature. | ||
### Headerless (optional) | ||
```javascript | ||
var md = require('markdown-it')() | ||
.use(require('markdown-it-multimd-table'), {enableRowspan: true}); | ||
Table header can be eliminated. | ||
```markdown | ||
|--|--|--|--|--|--|--|--| | ||
|♜| |♝|♛|♚|♝|♞|♜| | ||
| |♟|♟|♟| |♟|♟|♟| | ||
|♟| |♞| | | | | | | ||
| |♗| | |♟| | | | | ||
| | | | |♙| | | | | ||
| | | | | |♘| | | | ||
|♙|♙|♙|♙| |♙|♙|♙| | ||
|♖|♘|♗|♕|♔| | |♖| | ||
``` | ||
If this option is enabled, code above would be parsed as: | ||
<table> | ||
<tbody> | ||
<tr> | ||
<td>♜</td> | ||
<td></td> | ||
<td>♝</td> | ||
<td>♛</td> | ||
<td>♚</td> | ||
<td>♝</td> | ||
<td>♞</td> | ||
<td>♜</td> | ||
</tr> | ||
<tr> | ||
<td></td> | ||
<td>♟</td> | ||
<td>♟</td> | ||
<td>♟</td> | ||
<td></td> | ||
<td>♟</td> | ||
<td>♟</td> | ||
<td>♟</td> | ||
</tr> | ||
<tr> | ||
<td>♟</td> | ||
<td></td> | ||
<td>♞</td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<td></td> | ||
<td>♗</td> | ||
<td></td> | ||
<td></td> | ||
<td>♟</td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td>♙</td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td>♘</td> | ||
<td></td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<td>♙</td> | ||
<td>♙</td> | ||
<td>♙</td> | ||
<td>♙</td> | ||
<td></td> | ||
<td>♙</td> | ||
<td>♙</td> | ||
<td>♙</td> | ||
</tr> | ||
<tr> | ||
<td>♖</td> | ||
<td>♘</td> | ||
<td>♗</td> | ||
<td>♕</td> | ||
<td>♔</td> | ||
<td></td> | ||
<td></td> | ||
<td>♖</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
## Credits | ||
* [MultiMarkdown](https://fletcher.github.io/MultiMarkdown-6/), Lightweight markup processor to produce HTML, LaTeX, and more. | ||
@@ -197,0 +352,0 @@ * [markdown-it](https://markdown-it.github.io/), Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed. |
'use strict'; | ||
var path = require('path'); | ||
@@ -7,36 +8,22 @@ var generate = require('markdown-it-testgen'); | ||
describe('Basic', function () { | ||
describe('markdown-it-multimd-table-standard', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../')); | ||
generate(path.join(__dirname, 'fixtures/basic.txt'), md); | ||
generate(path.join(__dirname, 'fixtures/standard.txt'), {header: true}, md); | ||
}); | ||
describe('Requirements', function () { | ||
describe('markdown-it-multimd-table-unspecified', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../')); | ||
generate(path.join(__dirname, 'fixtures/requirements.txt'), md); | ||
generate(path.join(__dirname, 'fixtures/unspecified.txt'), {header: true}, md); | ||
}); | ||
describe('Other Notes', function () { | ||
describe('markdown-it-multimd-table-options', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../')); | ||
generate(path.join(__dirname, 'fixtures/notes.txt'), md); | ||
.use(require('../'), { | ||
multiline: true, | ||
rowspan: true, | ||
headerless: true, | ||
}); | ||
generate(path.join(__dirname, 'fixtures/options.txt'), {header: true}, md); | ||
}); | ||
describe('Issues', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../')); | ||
generate(path.join(__dirname, 'fixtures/issues.txt'), md); | ||
}); | ||
describe('(optional) Multilines', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../'), { enableMultilineRows: true }); | ||
generate(path.join(__dirname, 'fixtures/multilines.txt'), md); | ||
}); | ||
describe('(optional) Rowspans', function () { | ||
var md = require('markdown-it')() | ||
.use(require('../'), { enableMultilineRows: true, enableRowspan: true }); | ||
generate(path.join(__dirname, 'fixtures/rowspan.txt'), md); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
89441
794
347
18
1