1
0

initial html implementation

This commit is contained in:
Arpad Ryszka 2025-10-28 02:48:55 +01:00
parent ad3f63822a
commit ecb46a4b09
8 changed files with 322 additions and 10 deletions

2
go.mod
View File

@ -3,3 +3,5 @@ module code.squareroundforest.org/arpio/textfmt
go 1.25.0
require code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
require code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f // indirect

2
go.sum
View File

@ -1,2 +1,4 @@
code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f h1:Ep/POhkmvOfSkQklPIpeA4n2FTD2SoFxthjF0SJbsCU=
code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ=
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=

306
html.go Normal file
View File

@ -0,0 +1,306 @@
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)
}

1
html_test.go Normal file
View File

@ -0,0 +1 @@
package textfmt_test

9
lib.go
View File

@ -284,12 +284,13 @@ func Markdown(out io.Writer, d Document) error {
return renderMarkdown(out, d)
}
func HTML(io.Writer, Document) error {
func HTMLFragment(out io.Writer, doc Document) error {
// with the won HTML library
return nil
return renderHTMLFragment(out, doc)
}
func HTMLFragment(io.Writer, Document) error {
// if lang is empty, the lang attribute will be omitted
func HTML(out io.Writer, doc Document, lang string) error {
// with the won HTML library
return nil
return renderHTML(out, doc, lang)
}

View File

@ -429,8 +429,6 @@ func renderMarkdown(out io.Writer, d Document) error {
}
switch e.typ {
case invalid:
return errors.New("invalid entry")
case title:
renderMDTitle(&w, e)
case paragraph:
@ -449,6 +447,8 @@ func renderMarkdown(out io.Writer, d Document) error {
renderMDCode(&w, e)
case syntax:
renderMDSyntax(&w, e)
default:
return errors.New("invalid entry")
}
}

View File

@ -463,8 +463,6 @@ func renderRoff(out io.Writer, d Document) error {
}
switch e.typ {
case invalid:
return errors.New("invalid entry")
case title:
renderRoffTitle(&w, e)
case paragraph:
@ -483,6 +481,8 @@ func renderRoff(out io.Writer, d Document) error {
renderRoffCode(&w, e)
case syntax:
renderRoffSyntax(&w, e)
default:
return errors.New("invalid entry")
}
}

View File

@ -402,8 +402,6 @@ func renderTeletype(out io.Writer, d Document) error {
}
switch e.typ {
case invalid:
return errors.New("invalid entry")
case title:
renderTTYTitle(&w, e)
case paragraph:
@ -422,6 +420,8 @@ func renderTeletype(out io.Writer, d Document) error {
renderTTYCode(&w, e)
case syntax:
renderTTYSyntax(&w, e)
default:
return errors.New("invalid entry")
}
}