1
0
textfmt/html.go

307 lines
5.4 KiB
Go
Raw Normal View History

2025-10-28 02:48:55 +01:00
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
}
if t.link != "" {
return []any{tag.A(html.Attr("href", t.link), t.text)}
}
return []any{t.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(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)}, htmlText(definition.name)...)...),
tag.Dd(htmlText(definition.value)...),
)
}
return list
}
func htmlTable(e Entry) html.Tag {
table := tag.Table
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 {
ss := htmlSyntaxItems(s.sequence)
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
}
for i, tag := range tags {
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.RenderIndent(out, indent, tag); 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,
}
return html.RenderIndent(out, indent, tag.Doctype("html"), htmlDoc)
}