using fandoc::DocElem
using fandoc::DocWriter
using fandoc::FandocParser
** A 'PreProcessor' for rendering tables to HTML.
@Js
class TableProcessor : PreProcessor {
private TableParser tableParser := TableParser()
** Hook for rendering cell text. Just returns 'text.toXml' by default.
|Str->Str| textRenderer := |Str text->Str| { text.toXml }
Str table := "<table>"
Str tableEnd := "</table>"
Str thead := "<thead>"
Str theadEnd := "</thead>"
Str tbody := "<tbody>"
Str tbodyEnd := "</tbody>"
Str tr := "<tr>"
Str trEnd := "</tr>"
Str th := "<th>"
Str thEnd := "</th>"
Str td := "<td>"
Str tdEnd := "</td>"
@NoDoc
override Obj? process(HtmlElem elem, Uri cmd, Str preText) {
rows := tableParser.parseTable(preText.splitLines)
str := StrBuf()
out := str.out
out.print(table)
if (!rows.first.isEmpty) {
out.print(thead)
out.print(tr)
rows.first.each {
out.print(th)
out.print(textRenderer(it))
out.print(thEnd)
}
out.print(trEnd)
out.print(theadEnd)
}
out.print(tbody)
rows.eachRange(1..-1) |row| {
out.print(tr)
row.each {
out.print(td)
out.print(textRenderer(it))
out.print(tdEnd)
}
out.print(trEnd)
}
out.print(tbodyEnd)
out.print(tableEnd)
return str.toStr
}
}
** Parses table text into a 2D array of strings.
@Js
const class TableParser {
** Parses the table text.
Str[][] parseTable(Str[] lines) {
ctrl := (Str) (lines.find { it.trim.startsWith("-") } ?: throw ParseErr("Could not find table syntax in:\n" + lines.join("\n")))
// find the ranges of the ---'s
colRanges := Range[,]
last := 0
while (last < ctrl.size && ctrl.index("-", last) != null) {
start := ctrl.index("-", last)
end := start
while (end < ctrl.size && ctrl[end] == '-') end++
dashes := ctrl[start..<end]
if (!dashes.trim.isEmpty)
colRanges.add(start..<end)
last = end
}
// extend the ranges to the start of the next - dash
colRanges = colRanges.map |col, i -> Range| {
next := colRanges.getSafe(i+1)
end := next == null ? Int.maxVal : next.start - 1
return col.start..<end
}
inHeader := true
headers := Str[,]
rows := Str[][,]
lines.each |line| {
if (line.trim.isEmpty)
return
if (inHeader) {
if (line.trim.startsWith("-")) {
inHeader = false
return
}
colRanges.each |col, i| {
header := getTableData(line, col)
if (header != null)
if (i < headers.size)
headers[i] = "${headers[i]} ${header}".trim
else
headers.add(header)
}
} else {
row := Str[,]
colRanges.each |col| {
data := getTableData(line, col)
if (data != null)
row.add(data)
}
if (!row.isEmpty)
rows.add(row)
}
}
return rows.insert(0, headers)
}
private Str? getTableData(Str line, Range col) {
data := (Str?) Str.defVal
if (col.start < line.size)
if (col.end < line.size)
data = line.getRange(col).trim
else
data = line.getRange(col.start..-1).trim
if (data.isEmpty)
return data
// special case for fancy tables - needed for the last column where we grab all we can
if (!data.isEmpty && data[-1] == '|')
data = data[0..<-1].trim
return data.chars.all { it == '-' || it == '=' || it == '|' || it == '+' || it.isSpace } ? null : data
}
}