345 lines
5.9 KiB
Go
345 lines
5.9 KiB
Go
package textfmt
|
|
|
|
import (
|
|
"code.squareroundforest.org/arpio/html"
|
|
"code.squareroundforest.org/arpio/html/tag"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
func htmlText(t Txt) []any {
|
|
if len(t.cat) > 0 {
|
|
var c []any
|
|
for _, ti := range t.cat {
|
|
c = append(c, htmlText(ti)...)
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
var text any = t.text
|
|
if t.link != "" {
|
|
text = tag.A(html.Attr("href", t.link), text)
|
|
}
|
|
|
|
if t.bold {
|
|
text = tag.B(text)
|
|
}
|
|
|
|
if t.italic {
|
|
text = tag.I(text)
|
|
}
|
|
|
|
return []any{text}
|
|
}
|
|
|
|
func htmlTitle(e Entry) html.Tag {
|
|
h := tag.H6
|
|
switch e.titleLevel {
|
|
case 0:
|
|
h = tag.H1
|
|
case 1:
|
|
h = tag.H2
|
|
case 2:
|
|
h = tag.H3
|
|
case 3:
|
|
h = tag.H4
|
|
case 4:
|
|
h = tag.H5
|
|
}
|
|
|
|
return h(htmlText(e.text)...)
|
|
}
|
|
|
|
func htmlParagraph(e Entry) html.Tag {
|
|
return tag.P(htmlText(e.text)...)
|
|
}
|
|
|
|
func htmlList(e Entry) html.Tag {
|
|
list := tag.Ul
|
|
for _, item := range e.items {
|
|
list = list(tag.Li(htmlText(item.text)...))
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
func htmlNumberedList(e Entry) html.Tag {
|
|
list := tag.Ol
|
|
for _, item := range e.items {
|
|
list = list(tag.Li(htmlText(item.text)...))
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
func htmlDefinitions(e Entry) html.Tag {
|
|
list := tag.Dl
|
|
for _, definition := range e.definitions {
|
|
list = list(
|
|
tag.Dt(append(htmlText(definition.name), ":")...),
|
|
tag.Dd(htmlText(definition.value)...),
|
|
)
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
func htmlNumberedDefinitions(e Entry) html.Tag {
|
|
list := tag.Dl
|
|
for i, definition := range e.definitions {
|
|
list = list(
|
|
tag.Dt(append([]any{fmt.Sprintf("%d. ", i+1)}, append(htmlText(definition.name), ":")...)...),
|
|
tag.Dd(htmlText(definition.value)...),
|
|
)
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
func htmlTable(e Entry) html.Tag {
|
|
table := tag.Table
|
|
e.rows = normalizeTable(e.rows)
|
|
for _, r := range e.rows {
|
|
row := tag.Tr
|
|
cell := tag.Td
|
|
if r.header {
|
|
cell = tag.Th
|
|
}
|
|
|
|
for _, c := range r.cells {
|
|
row = row(cell(htmlText(c.text)...))
|
|
}
|
|
|
|
table = table(row)
|
|
}
|
|
|
|
return table
|
|
}
|
|
|
|
func htmlCode(e Entry) html.Tag {
|
|
return tag.Pre(tag.Code(htmlText(e.text)...))
|
|
}
|
|
|
|
func htmlMultiple(s SyntaxItem) string {
|
|
s.topLevel = false
|
|
s.multiple = false
|
|
return fmt.Sprintf("%s...", htmlSyntaxItem(s))
|
|
}
|
|
|
|
func htmlRequired(s SyntaxItem) string {
|
|
s.delimited = true
|
|
s.topLevel = false
|
|
s.required = false
|
|
return fmt.Sprintf("<%s>", htmlSyntaxItem(s))
|
|
}
|
|
|
|
func htmlOptional(s SyntaxItem) string {
|
|
s.delimited = true
|
|
s.topLevel = false
|
|
s.optional = false
|
|
return fmt.Sprintf("[%s]", htmlSyntaxItem(s))
|
|
}
|
|
|
|
func htmlSequence(s SyntaxItem) string {
|
|
ss := htmlSyntaxItems(s.sequence)
|
|
if s.delimited || s.topLevel {
|
|
return strings.Join(ss, " ")
|
|
}
|
|
|
|
return fmt.Sprintf("(%s)", strings.Join(ss, " "))
|
|
}
|
|
|
|
func htmlChoice(s SyntaxItem) string {
|
|
items := make([]SyntaxItem, len(s.choice))
|
|
copy(items, s.choice)
|
|
for i := range items {
|
|
items[i].topLevel = s.topLevel
|
|
}
|
|
|
|
ss := htmlSyntaxItems(items)
|
|
if s.topLevel {
|
|
return strings.Join(ss, "\n")
|
|
}
|
|
|
|
if s.delimited {
|
|
return strings.Join(ss, "|")
|
|
}
|
|
|
|
return fmt.Sprintf("(%s)", strings.Join(ss, "|"))
|
|
}
|
|
|
|
func htmlSymbol(s SyntaxItem) string {
|
|
return s.symbol
|
|
}
|
|
|
|
func htmlSyntaxItem(s SyntaxItem) string {
|
|
switch {
|
|
|
|
// foo...
|
|
case s.multiple:
|
|
return htmlMultiple(s)
|
|
|
|
// <foo>
|
|
case s.required:
|
|
return htmlRequired(s)
|
|
|
|
// [foo]
|
|
case s.optional:
|
|
return htmlOptional(s)
|
|
|
|
// foo bar baz or (foo bar baz)
|
|
case len(s.sequence) > 0:
|
|
return htmlSequence(s)
|
|
|
|
// foo|bar|baz or (foo|bar|baz)
|
|
case len(s.choice) > 0:
|
|
return htmlChoice(s)
|
|
|
|
// foo
|
|
default:
|
|
return htmlSymbol(s)
|
|
}
|
|
}
|
|
|
|
func htmlSyntaxItems(s []SyntaxItem) []string {
|
|
var ss []string
|
|
for _, si := range s {
|
|
si.delimited = false
|
|
ss = append(ss, htmlSyntaxItem(si))
|
|
}
|
|
|
|
return ss
|
|
}
|
|
|
|
func htmlSyntax(e Entry) html.Tag {
|
|
s := e.syntax
|
|
s.topLevel = true
|
|
return tag.Pre(htmlSyntaxItem(s))
|
|
}
|
|
|
|
func htmlTag(e Entry) (html.Tag, error) {
|
|
switch e.typ {
|
|
case title:
|
|
return htmlTitle(e), nil
|
|
case paragraph:
|
|
return htmlParagraph(e), nil
|
|
case list:
|
|
return htmlList(e), nil
|
|
case numberedList:
|
|
return htmlNumberedList(e), nil
|
|
case definitions:
|
|
return htmlDefinitions(e), nil
|
|
case numberedDefinitions:
|
|
return htmlNumberedDefinitions(e), nil
|
|
case table:
|
|
return htmlTable(e), nil
|
|
case code:
|
|
return htmlCode(e), nil
|
|
case syntax:
|
|
return htmlSyntax(e), nil
|
|
default:
|
|
return nil, errors.New("invalid entry")
|
|
}
|
|
}
|
|
|
|
func htmlTags(e []Entry) ([]html.Tag, error) {
|
|
var tags []html.Tag
|
|
for _, ei := range e {
|
|
tag, err := htmlTag(ei)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if tag != nil {
|
|
tags = append(tags, tag)
|
|
}
|
|
}
|
|
|
|
return tags, nil
|
|
}
|
|
|
|
func renderHTMLFragment(out io.Writer, doc Document) error {
|
|
tags, err := htmlTags(doc.entries)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(tags) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for i, tag := range tags {
|
|
if i > 0 {
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
indent := html.Indentation{
|
|
Indent: "\t",
|
|
PWidth: 120,
|
|
MinPWidth: 60,
|
|
}
|
|
|
|
if doc.entries[i].wrapWidth != 0 {
|
|
indent.PWidth = doc.entries[i].wrapWidth
|
|
indent.MinPWidth = indent.PWidth / 2
|
|
}
|
|
|
|
if doc.entries[i].indent != 0 {
|
|
indent.Indent = timesn(" ", doc.entries[i].indent)
|
|
}
|
|
|
|
if err := html.WriteIndent(out, indent, tag); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func renderHTML(out io.Writer, doc Document, lang string) error {
|
|
tags, err := htmlTags(doc.entries)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
head := tag.Head(tag.Meta(html.Attr("charset", "utf-8")))
|
|
if len(doc.entries) > 0 && doc.entries[0].typ == title && doc.entries[0].titleLevel == 0 {
|
|
head = head(tag.Title(htmlText(doc.entries[0].text)...))
|
|
}
|
|
|
|
body := tag.Body
|
|
for _, tag := range tags {
|
|
body = body(tag)
|
|
}
|
|
|
|
htmlDoc := tag.Html(head, body)
|
|
if lang != "" {
|
|
htmlDoc = htmlDoc(html.Attr("lang", lang))
|
|
}
|
|
|
|
indent := html.Indentation{
|
|
Indent: "\t",
|
|
PWidth: 120,
|
|
MinPWidth: 60,
|
|
}
|
|
|
|
if err := html.WriteIndent(out, indent, tag.Doctype("html"), htmlDoc); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|