mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-06 01:50: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
|
||||
buf string
|
||||
// Temporaries
|
||||
img *Img
|
||||
img *Img
|
||||
table *Table
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
@ -80,6 +81,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
switch state.where {
|
||||
case "img":
|
||||
goto imgState
|
||||
case "table":
|
||||
goto tableState
|
||||
case "pre":
|
||||
goto preformattedState
|
||||
case "list":
|
||||
@ -99,6 +102,13 @@ imgState:
|
||||
}
|
||||
return
|
||||
|
||||
tableState:
|
||||
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
|
||||
state.where = ""
|
||||
addLine(*state.table)
|
||||
}
|
||||
return
|
||||
|
||||
preformattedState:
|
||||
switch {
|
||||
case startsWith("```"):
|
||||
@ -209,6 +219,9 @@ normalState:
|
||||
state.where = "img"
|
||||
state.img = img
|
||||
}
|
||||
case MatchesTable(line):
|
||||
state.where = "table"
|
||||
state.table = TableFromFirstLine(line, state.name)
|
||||
default:
|
||||
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)
|
||||
case Img:
|
||||
html += v.ToHtml()
|
||||
case Table:
|
||||
html += v.asHtml()
|
||||
case string:
|
||||
html += v
|
||||
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__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
|
||||
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__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