package org
import (
"fmt"
"html"
"strings"
)
type HTMLWriter struct {
stringBuilder
document *Document
HighlightCodeBlock func(source, lang string) string
}
var emphasisTags = map[string][]string{
"/": []string{"", ""},
"*": []string{"", ""},
"+": []string{"", ""},
"~": []string{"", "
"},
"=": []string{``, "
"},
"_": []string{``, ""},
"_{}": []string{"", ""},
"^{}": []string{"", ""},
}
var listTags = map[string][]string{
"+": []string{"
"},
"-": []string{""},
"*": []string{""},
"number": []string{"", "
"},
"letter": []string{"", "
"},
}
func NewHTMLWriter() *HTMLWriter {
return &HTMLWriter{
HighlightCodeBlock: func(source, lang string) string {
return fmt.Sprintf(``, html.EscapeString(source))
},
}
}
func (w *HTMLWriter) emptyClone() *HTMLWriter {
wcopy := *w
wcopy.stringBuilder = stringBuilder{}
return &wcopy
}
func (w *HTMLWriter) before(d *Document) {
w.document = d
}
func (w *HTMLWriter) after(d *Document) {
w.writeFootnotes(d)
}
func (w *HTMLWriter) writeNodes(ns ...Node) {
for _, n := range ns {
switch n := n.(type) {
case Keyword, Comment:
continue
case NodeWithMeta:
w.writeNodeWithMeta(n)
case Headline:
w.writeHeadline(n)
case Block:
w.writeBlock(n)
case FootnoteDefinition:
w.writeFootnoteDefinition(n)
case List:
w.writeList(n)
case ListItem:
w.writeListItem(n)
case Table:
w.writeTable(n)
case TableHeader:
w.writeTableHeader(n)
case TableRow:
w.writeTableRow(n)
case TableSeparator:
w.writeTableSeparator(n)
case Paragraph:
w.writeParagraph(n)
case HorizontalRule:
w.writeHorizontalRule(n)
case Text:
w.writeText(n)
case Emphasis:
w.writeEmphasis(n)
case ExplicitLineBreak:
w.writeExplicitLineBreak(n)
case LineBreak:
w.writeLineBreak(n)
case RegularLink:
w.writeRegularLink(n)
case FootnoteLink:
w.writeFootnoteLink(n)
default:
if n != nil {
panic(fmt.Sprintf("bad node %#v", n))
}
}
}
}
func (w *HTMLWriter) writeBlock(b Block) {
switch b.Name {
case "SRC":
source, lang := b.Children[0].(Text).Content, "text"
if len(b.Parameters) >= 1 {
lang = b.Parameters[0]
}
w.WriteString(w.HighlightCodeBlock(source, lang) + "\n")
case "EXAMPLE":
w.WriteString(`` + "\n")
w.writeNodes(b.Children...)
w.WriteString("\n
\n")
case "QUOTE":
w.WriteString("\n")
w.writeNodes(b.Children...)
w.WriteString("
\n")
case "CENTER":
w.WriteString(`` + "\n")
w.writeNodes(b.Children...)
w.WriteString("
\n")
default:
w.WriteString(fmt.Sprintf(``, strings.ToLower(b.Name)) + "\n")
w.writeNodes(b.Children...)
w.WriteString("
\n")
}
}
func (w *HTMLWriter) writeFootnoteDefinition(f FootnoteDefinition) {
n := f.Name
w.WriteString(`\n")
}
func (w *HTMLWriter) writeFootnotes(d *Document) {
fs := d.Footnotes
if len(fs.Definitions) == 0 {
return
}
w.WriteString(`\n")
}
func (w *HTMLWriter) writeHeadline(h Headline) {
w.WriteString(fmt.Sprintf("", h.Lvl))
w.writeNodes(h.Title...)
w.WriteString(fmt.Sprintf("\n", h.Lvl))
w.writeNodes(h.Children...)
}
func (w *HTMLWriter) writeText(t Text) {
w.WriteString(html.EscapeString(t.Content))
}
func (w *HTMLWriter) writeEmphasis(e Emphasis) {
tags, ok := emphasisTags[e.Kind]
if !ok {
panic(fmt.Sprintf("bad emphasis %#v", e))
}
w.WriteString(tags[0])
w.writeNodes(e.Content...)
w.WriteString(tags[1])
}
func (w *HTMLWriter) writeLineBreak(l LineBreak) {
w.WriteString("\n")
}
func (w *HTMLWriter) writeExplicitLineBreak(l ExplicitLineBreak) {
w.WriteString("
\n")
}
func (w *HTMLWriter) writeFootnoteLink(l FootnoteLink) {
n := html.EscapeString(l.Name)
w.WriteString(fmt.Sprintf(``, n, n, n))
}
func (w *HTMLWriter) writeRegularLink(l RegularLink) {
url := html.EscapeString(l.URL)
if l.Protocol == "file" {
url = url[len("file:"):]
}
description := url
if l.Description != nil {
descriptionWriter := w.emptyClone()
descriptionWriter.writeNodes(l.Description...)
description = descriptionWriter.String()
}
switch l.Kind() {
case "image":
w.WriteString(fmt.Sprintf(`
`, url, description, description))
case "video":
w.WriteString(fmt.Sprintf(``, url, description, description))
default:
w.WriteString(fmt.Sprintf(`%s`, url, description))
}
}
func (w *HTMLWriter) writeList(l List) {
tags, ok := listTags[l.Kind]
if !ok {
panic(fmt.Sprintf("bad list kind %#v", l))
}
w.WriteString(tags[0] + "\n")
w.writeNodes(l.Items...)
w.WriteString(tags[1] + "\n")
}
func (w *HTMLWriter) writeListItem(li ListItem) {
w.WriteString("\n")
w.writeNodes(li.Children...)
w.WriteString("\n")
}
func (w *HTMLWriter) writeParagraph(p Paragraph) {
if isEmptyLineParagraph(p) {
return
}
w.WriteString("")
if _, ok := p.Children[0].(LineBreak); !ok {
w.WriteString("\n")
}
w.writeNodes(p.Children...)
w.WriteString("\n
\n")
}
func (w *HTMLWriter) writeHorizontalRule(h HorizontalRule) {
w.WriteString("
\n")
}
func (w *HTMLWriter) writeNodeWithMeta(m NodeWithMeta) {
nodeW := w.emptyClone()
nodeW.writeNodes(m.Node)
nodeString := nodeW.String()
if rawCaption, ok := m.Meta["CAPTION"]; ok {
nodes, captionW := w.document.parseInline(rawCaption), w.emptyClone()
captionW.writeNodes(nodes...)
caption := `` + "\n" + captionW.String() + "\n
\n"
nodeString = `` + "\n" + nodeString + caption + `
` + "\n"
}
w.WriteString(nodeString)
}
func (w *HTMLWriter) writeTable(t Table) {
w.WriteString("\n")
w.writeNodes(t.Header)
w.WriteString("\n")
w.writeNodes(t.Rows...)
w.WriteString("\n
\n")
}
func (w *HTMLWriter) writeTableRow(t TableRow) {
w.WriteString("\n")
for _, column := range t.Columns {
w.WriteString("")
w.writeNodes(column...)
w.WriteString(" | ")
}
w.WriteString("\n
\n")
}
func (w *HTMLWriter) writeTableHeader(t TableHeader) {
w.WriteString("\n")
for _, column := range t.Columns {
w.WriteString("")
w.writeNodes(column...)
w.WriteString(" | ")
}
w.WriteString("\n\n")
}
func (w *HTMLWriter) writeTableSeparator(t TableSeparator) {
w.WriteString("
\n")
}