mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-07 10:20:26 +00:00
Add tables to mycomarkup
This commit is contained in:
parent
30c95dd037
commit
4d2be01b85
@ -27,7 +27,8 @@ type GemLexerState struct {
|
|||||||
id int
|
id int
|
||||||
buf string
|
buf string
|
||||||
// Temporaries
|
// Temporaries
|
||||||
img *Img
|
img *Img
|
||||||
|
table *Table
|
||||||
}
|
}
|
||||||
|
|
||||||
type Line struct {
|
type Line struct {
|
||||||
@ -80,6 +81,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) {
|
|||||||
switch state.where {
|
switch state.where {
|
||||||
case "img":
|
case "img":
|
||||||
goto imgState
|
goto imgState
|
||||||
|
case "table":
|
||||||
|
goto tableState
|
||||||
case "pre":
|
case "pre":
|
||||||
goto preformattedState
|
goto preformattedState
|
||||||
case "list":
|
case "list":
|
||||||
@ -99,6 +102,13 @@ imgState:
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
tableState:
|
||||||
|
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
|
||||||
|
state.where = ""
|
||||||
|
addLine(*state.table)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
preformattedState:
|
preformattedState:
|
||||||
switch {
|
switch {
|
||||||
case startsWith("```"):
|
case startsWith("```"):
|
||||||
@ -209,6 +219,9 @@ normalState:
|
|||||||
state.where = "img"
|
state.where = "img"
|
||||||
state.img = img
|
state.img = img
|
||||||
}
|
}
|
||||||
|
case MatchesTable(line):
|
||||||
|
state.where = "table"
|
||||||
|
state.table = TableFromFirstLine(line, state.name)
|
||||||
default:
|
default:
|
||||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
|
|||||||
html += Transclude(v, recursionLevel)
|
html += Transclude(v, recursionLevel)
|
||||||
case Img:
|
case Img:
|
||||||
html += v.ToHtml()
|
html += v.ToHtml()
|
||||||
|
case Table:
|
||||||
|
html += v.asHtml()
|
||||||
case string:
|
case string:
|
||||||
html += v
|
html += v
|
||||||
default:
|
default:
|
||||||
|
215
markup/table.go
Normal file
215
markup/table.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
// "github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tableRe = regexp.MustCompile(`^table\s+{`)
|
||||||
|
|
||||||
|
func MatchesTable(line string) bool {
|
||||||
|
return tableRe.MatchString(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TableFromFirstLine(line, hyphaName string) *Table {
|
||||||
|
return &Table{
|
||||||
|
hyphaName: hyphaName,
|
||||||
|
caption: line[strings.IndexRune(line, '{')+1:],
|
||||||
|
rows: make([]*tableRow, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Process(line string) (shouldGoBackToNormal bool) {
|
||||||
|
if strings.TrimSpace(line) == "}" && !t.inMultiline {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !t.inMultiline {
|
||||||
|
t.pushRow()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
escaping bool
|
||||||
|
lookingForNonSpace = !t.inMultiline
|
||||||
|
countingColspan bool
|
||||||
|
)
|
||||||
|
for i, r := range line {
|
||||||
|
switch {
|
||||||
|
case lookingForNonSpace && unicode.IsSpace(r):
|
||||||
|
case lookingForNonSpace && (r == '!' || r == '|'):
|
||||||
|
t.currCellMarker = r
|
||||||
|
t.currColspan = 1
|
||||||
|
lookingForNonSpace = false
|
||||||
|
countingColspan = true
|
||||||
|
case lookingForNonSpace:
|
||||||
|
t.currCellMarker = '^' // ^ represents implicit |, not part of syntax
|
||||||
|
t.currColspan = 1
|
||||||
|
lookingForNonSpace = false
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
case escaping:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
case t.inMultiline && r == '}':
|
||||||
|
t.inMultiline = false
|
||||||
|
case t.inMultiline && i == len(line)-1:
|
||||||
|
t.currCellBuilder.WriteRune('\n')
|
||||||
|
case t.inMultiline:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
// Not in multiline:
|
||||||
|
case (r == '|' || r == '!') && !countingColspan:
|
||||||
|
t.pushCell()
|
||||||
|
t.currCellMarker = r
|
||||||
|
t.currColspan = 1
|
||||||
|
countingColspan = true
|
||||||
|
case r == t.currCellMarker && (r == '|' || r == '!') && countingColspan:
|
||||||
|
t.currColspan++
|
||||||
|
case r == '{':
|
||||||
|
t.inMultiline = true
|
||||||
|
countingColspan = false
|
||||||
|
case i == len(line)-1:
|
||||||
|
t.pushCell()
|
||||||
|
default:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
countingColspan = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Table struct {
|
||||||
|
// data
|
||||||
|
hyphaName string
|
||||||
|
caption string
|
||||||
|
rows []*tableRow
|
||||||
|
// state
|
||||||
|
inMultiline bool
|
||||||
|
// tmp
|
||||||
|
currCellMarker rune
|
||||||
|
currColspan uint
|
||||||
|
currCellBuilder strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) pushRow() {
|
||||||
|
t.rows = append(t.rows, &tableRow{
|
||||||
|
cells: make([]*tableCell, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) pushCell() {
|
||||||
|
tc := &tableCell{
|
||||||
|
content: t.currCellBuilder.String(),
|
||||||
|
colspan: t.currColspan,
|
||||||
|
}
|
||||||
|
switch t.currCellMarker {
|
||||||
|
case '|', '^':
|
||||||
|
tc.kind = tableCellDatum
|
||||||
|
case '!':
|
||||||
|
tc.kind = tableCellHeader
|
||||||
|
}
|
||||||
|
// We expect the table to have at least one row ready, so no nil-checking
|
||||||
|
tr := t.rows[len(t.rows)-1]
|
||||||
|
tr.cells = append(tr.cells, tc)
|
||||||
|
t.currCellBuilder = strings.Builder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) asHtml() (html string) {
|
||||||
|
if t.caption != "" {
|
||||||
|
html += fmt.Sprintf("<caption>%s</caption>", t.caption)
|
||||||
|
}
|
||||||
|
if len(t.rows) > 0 && t.rows[0].looksLikeThead() {
|
||||||
|
html += fmt.Sprintf("<thead>%s</thead>", t.rows[0].asHtml(t.hyphaName))
|
||||||
|
t.rows = t.rows[1:]
|
||||||
|
}
|
||||||
|
html += "\n<tbody>\n"
|
||||||
|
for _, tr := range t.rows {
|
||||||
|
html += tr.asHtml(t.hyphaName)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`<table>%s</tbody></table>`, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableRow struct {
|
||||||
|
cells []*tableCell
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *tableRow) asHtml(hyphaName string) (html string) {
|
||||||
|
for _, tc := range tr.cells {
|
||||||
|
html += tc.asHtml(hyphaName)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<tr>%s</tr>\n", html)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most likely, rows with more than two header cells are theads. I allow one extra datum cell for tables like this:
|
||||||
|
// | ! a ! b
|
||||||
|
// ! c | d | e
|
||||||
|
// ! f | g | h
|
||||||
|
func (tr *tableRow) looksLikeThead() bool {
|
||||||
|
var (
|
||||||
|
headerAmount = 0
|
||||||
|
datumAmount = 0
|
||||||
|
)
|
||||||
|
for _, tc := range tr.cells {
|
||||||
|
switch tc.kind {
|
||||||
|
case tableCellHeader:
|
||||||
|
headerAmount++
|
||||||
|
case tableCellDatum:
|
||||||
|
datumAmount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headerAmount >= 2 && datumAmount <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableCell struct {
|
||||||
|
kind tableCellKind
|
||||||
|
colspan uint
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) asHtml(hyphaName string) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"<%[1]s %[2]s>%[3]s</%[1]s>\n",
|
||||||
|
tc.kind.tagName(),
|
||||||
|
tc.colspanAttribute(),
|
||||||
|
tc.contentAsHtml(hyphaName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) colspanAttribute() string {
|
||||||
|
if tc.colspan <= 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`colspan="%d"`, tc.colspan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) contentAsHtml(hyphaName string) (html string) {
|
||||||
|
for _, line := range strings.Split(tc.content, "\n") {
|
||||||
|
if line = strings.TrimSpace(line); line != "" {
|
||||||
|
if html != "" {
|
||||||
|
html += `<br>`
|
||||||
|
}
|
||||||
|
html += ParagraphToHtml(hyphaName, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableCellKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableCellUnknown tableCellKind = iota
|
||||||
|
tableCellHeader
|
||||||
|
tableCellDatum
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tck tableCellKind) tagName() string {
|
||||||
|
switch tck {
|
||||||
|
case tableCellHeader:
|
||||||
|
return "th"
|
||||||
|
case tableCellDatum:
|
||||||
|
return "td"
|
||||||
|
default:
|
||||||
|
return "p"
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 7828352598c19afe5f2e13df0219656ac7b44c9c
|
Subproject commit 95f48bfd7a7cfef17d56cef207a770767d727950
|
@ -93,6 +93,10 @@ nav ul li {list-style-type:none;margin-right:1rem;}
|
|||||||
.history-entry__time { font-weight: bold; }
|
.history-entry__time { font-weight: bold; }
|
||||||
.history-entry__author { font-style: italic; }
|
.history-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
table { background-color: #eee; border: #ddd 1px solid; border-radius: .25rem;
|
||||||
|
min-width: 4rem; }
|
||||||
|
td { padding: .25rem; border: #ddd 1px solid; }
|
||||||
|
caption { caption-side: top; font-size: small; }
|
||||||
`)
|
`)
|
||||||
//line templates/asset.qtpl:2
|
//line templates/asset.qtpl:2
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
@ -68,3 +68,7 @@ nav ul li {list-style-type:none;margin-right:1rem;}
|
|||||||
.history-entry__time { font-weight: bold; }
|
.history-entry__time { font-weight: bold; }
|
||||||
.history-entry__author { font-style: italic; }
|
.history-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
table { background-color: #eee; border: #ddd 1px solid; border-radius: .25rem;
|
||||||
|
min-width: 4rem; }
|
||||||
|
td { padding: .25rem; border: #ddd 1px solid; }
|
||||||
|
caption { caption-side: top; font-size: small; }
|
||||||
|
Loading…
Reference in New Issue
Block a user