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 { ss := htmlSyntaxItems(s.choice) 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) // 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 }