marktable
Version:
Markdown table parser, editor and formater
200 lines (174 loc) • 4.89 kB
JavaScript
// Import
var range = require('lodash.range')
var repeat = require('lodash.repeat')
var re = require('./regex')
var Row = require('./Row')
var MultilineRow = require('./MultilineRow')
var Colgroup = require('./Colgroup')
var TBody = require('./TBody')
// Export
module.exports = Table
function Table () {
this.id = undefined
this.caption = undefined
this.headers = []
this.colgroup = new Colgroup()
this.tbodies = []
this.styles = {
border: true
}
}
Table.prototype.parse = function (src, offset) {
this.rawTable = src
var match = re.wholeTable.exec(src)
if (!match) return false
var infos = {
offset: 0,
offsetEnd: match[0].length,
raw: match[0],
caption: match[1],
headers: match[2],
columns: match[3],
rows: match[4]
}
this.handleParseResult(infos)
return infos
}
Table.prototype.handleParseResult = function (pResult) {
var self = this
var match
// Read captions and id
if (pResult.caption) {
match = this._handleCaption(pResult.caption)
this.id = match[1] ? match[2].trim() : undefined
this.caption = match[1] ? match[1] : match[2]
}
// Read headers
if (pResult.headers) {
this.headers = this._handleHeaders(pResult.headers)
}
// Read columns
this.colgroup.parse(pResult.columns)
// Read rows
if (pResult.rows) {
pResult.rows.trim().split(/\n[\s.]*\n/g).forEach(function (body) {
var tbody = new TBody()
tbody.parse(body)
self.tbodies.push(tbody)
})
}
// Extract style information:
this.styles.border = /^|/.test(pResult.columns)
}
Table.prototype._handleCaption = function (caption) {
var reg = /^(?:\[([^\]]*?)\])?(?:\[([^\]]*?)\])[ \t]*/
var match = reg.exec(caption)
return match
}
Table.prototype._handleHeaders = function (headers) {
var result = []
headers.trim().split('\n').forEach(function (line) {
var row = new Row()
row.parse(line)
result.push(row)
})
return result
}
Table.prototype.toMarkdown = function () {
var self = this
self.normalize()
var out = []
// Caption and table id
if (self.caption) {
out.push('[' + self.caption + ']' + (self.id ? '[' + self.id + ']' : ''))
}
// Headers
self.headers.forEach(function (row) {
out.push(row.toMarkdown(self.colgroup))
})
// column alignment
out.push(self.colgroup.toMarkdown())
// Headers
var countTbody = self.tbodies.length
self.tbodies.forEach(function (tbody, i) {
out.push(tbody.toMarkdown(self.colgroup))
if ((countTbody - 1) !== i) {
out.push(repeat(' ', self.colgroup.getFullFormatedSize()))
}
})
return out.join('\n')
}
Table.prototype._getAllRow = function () {
var allRows = []
this.headers.forEach(function (row) {
allRows.push(row)
})
this.tbodies.forEach(function (tbody) {
tbody.rows.forEach(function (row) {
if (row instanceof MultilineRow) {
// TODO: put this in TBody
row.rows.forEach(function (row) {
allRows.push(row)
})
} else {
allRows.push(row)
}
})
})
return allRows
}
Table.prototype.normalize = function () {
var self = this
// Normalize columns size
var allRows = self._getAllRow()
var colgroup = self.colgroup
// First pass normal cell
allRows.forEach(function (row) {
var index = 0
row.cells.forEach(function (cell) {
if (cell.colspan === 1) {
var sizeNeeded = cell.getContentSize()
self.colgroup.upSizeTo(index, sizeNeeded)
}
index += cell.colspan
})
})
// Second pass (to adjust colspan cell)
allRows.forEach(function (row) {
var index = 0
row.cells.forEach(function (cell) {
if (cell.colspan > 1) {
var sizeNeeded = cell.getContentSize()
var currentSize = colgroup.getFormatedSizeAt(index, cell.colspan)
var remainSizeNeeded = sizeNeeded - currentSize
if (remainSizeNeeded > 0) {
var sharedSize = Math.ceil(remainSizeNeeded / cell.colspan)
range(index, index + cell.colspan).forEach(function (index) {
if (remainSizeNeeded <= 0) return
var cur = self.colgroup.getColumnAt(index).getSize()
var add = (remainSizeNeeded < sharedSize ? remainSizeNeeded : sharedSize)
var upto = cur + add
self.colgroup.upSizeTo(index, upto)
remainSizeNeeded -= add
})
}
}
index += cell.colspan
})
})
}
Table.prototype.getInfos = function () {
return {
id: this.id,
caption: this.caption,
headers: this.headers ? this.headers.length : 0,
columns: this.colgroup.columns.length,
bodies: this.tbodies.length,
rows: this.tbodies.reduce(function (memo, tbody) {
return memo + tbody.rows.length
}, 0),
lines: this.tbodies.reduce(function (memo, tbody) {
return memo + tbody.getInfos().lines
}, 0)
}
}