markdown/output.go

282 lines
6.3 KiB
Go
Raw Normal View History

2010-11-21 22:04:39 +00:00
/* Original C version https://github.com/jgm/peg-markdown/
* Copyright 2008 John MacFarlane (jgm at berkeley dot edu).
*
* Modifications and translation from C into Go
* based on markdown_output.c
* Copyright 2010 Michael Teichgräber (mt at wmipf dot de)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License or the MIT
* license. See LICENSE for details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package markdown
// HTML output functions
import (
"fmt"
"log"
"math/rand"
2010-11-21 22:04:39 +00:00
"strings"
)
type Writer interface {
Write([]byte) (int, error)
WriteString(string) (int, error)
WriteRune(rune) (int, error)
WriteByte(byte) error
2010-11-21 22:04:39 +00:00
}
type baseWriter struct {
2010-11-21 22:04:39 +00:00
Writer
padded int
}
type htmlOut struct {
baseWriter
obfuscate bool
2010-11-21 22:04:39 +00:00
notenum int
endNotes []*element /* List of endnotes to print after main content. */
2010-11-21 22:04:39 +00:00
}
func ToHTML(w Writer) Formatter {
f := new(htmlOut)
f.baseWriter = baseWriter{w, 2}
return f
}
func (f *htmlOut) FormatBlock(tree *element) {
f.elist(tree)
}
func (f *htmlOut) Finish() {
if len(f.endNotes) != 0 {
f.sp()
f.printEndnotes()
}
f.WriteByte('\n')
2012-08-31 22:25:33 +00:00
f.padded = 2
2010-11-21 22:04:39 +00:00
}
// pad - add a number of newlines, the value of the
// argument minus the value of `padded'
// One newline means a line break, similar to troff's .br
// request, two newlines mean a line break plus an
// empty line, similar to troff's .sp request
func (w *baseWriter) pad(n int) {
for ; n > w.padded; n-- {
w.WriteByte('\n')
2010-11-21 22:04:39 +00:00
}
w.padded = 0
2010-11-21 22:04:39 +00:00
}
func (h *htmlOut) br() *htmlOut {
h.pad(1)
return h
}
func (h *htmlOut) sp() *htmlOut {
h.pad(2)
return h
}
func (h *htmlOut) skipPadding() *htmlOut {
h.padded = 2
2010-11-21 22:04:39 +00:00
return h
}
// print a string
func (w *htmlOut) s(s string) *htmlOut {
w.WriteString(s)
return w
}
2013-01-23 20:51:00 +00:00
/* print string, escaping for HTML
2010-11-21 22:04:39 +00:00
* If obfuscate selected, convert characters to hex or decimal entities at random
*/
func (w *htmlOut) str(s string) *htmlOut {
2010-12-03 21:20:41 +00:00
var ws string
var i0 = 0
2012-04-29 22:58:06 +00:00
o := w.obfuscate
2010-12-03 21:20:41 +00:00
for i, r := range s {
2010-11-21 22:04:39 +00:00
switch r {
case '&':
2010-12-03 21:20:41 +00:00
ws = "&"
2010-11-21 22:04:39 +00:00
case '<':
2010-12-03 21:20:41 +00:00
ws = "&lt;"
2010-11-21 22:04:39 +00:00
case '>':
2010-12-03 21:20:41 +00:00
ws = "&gt;"
2010-11-21 22:04:39 +00:00
case '"':
2010-12-03 21:20:41 +00:00
ws = "&quot;"
2010-11-21 22:04:39 +00:00
default:
2012-04-29 22:58:06 +00:00
if o {
2012-04-26 19:23:45 +00:00
if rand.Intn(2) == 0 {
2010-12-03 21:20:41 +00:00
ws = fmt.Sprintf("&#%d;", r)
2010-11-21 22:04:39 +00:00
} else {
2010-12-03 21:20:41 +00:00
ws = fmt.Sprintf("&#%x;", r)
2010-11-21 22:04:39 +00:00
}
} else {
2010-12-03 21:20:41 +00:00
if i0 == -1 {
i0 = i
}
continue
2010-11-21 22:04:39 +00:00
}
}
2010-12-03 21:20:41 +00:00
if i0 != -1 {
w.WriteString(s[i0:i])
i0 = -1
}
w.WriteString(ws)
}
if i0 != -1 {
w.WriteString(s[i0:])
2010-11-21 22:04:39 +00:00
}
return w
}
2012-04-26 19:35:18 +00:00
func (w *htmlOut) children(el *element) *htmlOut {
return w.elist(el.children)
}
func (w *htmlOut) inline(tag string, el *element) *htmlOut {
return w.s(tag).children(el).s("</").s(tag[1:])
}
func (w *htmlOut) listBlock(tag string, el *element) *htmlOut {
return w.sp().s(tag).elist(el.children).br().s("</").s(tag[1:])
2012-04-26 19:35:18 +00:00
}
func (w *htmlOut) listItem(tag string, el *element) *htmlOut {
return w.br().s(tag).skipPadding().elist(el.children).s("</").s(tag[1:])
2012-04-26 19:35:18 +00:00
}
2010-11-21 22:04:39 +00:00
/* print a list of elements
*/
func (w *htmlOut) elist(list *element) *htmlOut {
2010-11-21 22:04:39 +00:00
for list != nil {
w.elem(list)
2010-11-21 22:04:39 +00:00
list = list.next
}
return w
}
// print an element
func (w *htmlOut) elem(elt *element) *htmlOut {
2010-11-21 22:04:39 +00:00
var s string
switch elt.key {
case SPACE:
s = elt.contents.str
case LINEBREAK:
s = "<br/>\n"
case STR:
w.str(elt.contents.str)
2010-11-21 22:04:39 +00:00
case ELLIPSIS:
s = "&hellip;"
case EMDASH:
s = "&mdash;"
case ENDASH:
s = "&ndash;"
case APOSTROPHE:
s = "&rsquo;"
case SINGLEQUOTED:
2012-04-26 19:35:18 +00:00
w.s("&lsquo;").children(elt).s("&rsquo;")
2010-11-21 22:04:39 +00:00
case DOUBLEQUOTED:
2012-04-26 19:35:18 +00:00
w.s("&ldquo;").children(elt).s("&rdquo;")
2010-11-21 22:04:39 +00:00
case CODE:
w.s("<code>").str(elt.contents.str).s("</code>")
2010-11-21 22:04:39 +00:00
case HTML:
s = elt.contents.str
case LINK:
o := w.obfuscate
2010-11-21 22:04:39 +00:00
if strings.Index(elt.contents.link.url, "mailto:") == 0 {
w.obfuscate = true /* obfuscate mailto: links */
2010-11-21 22:04:39 +00:00
}
w.s(`<a href="`).str(elt.contents.link.url).s(`"`)
2010-11-21 22:04:39 +00:00
if len(elt.contents.link.title) > 0 {
w.s(` title="`).str(elt.contents.link.title).s(`"`)
2010-11-21 22:04:39 +00:00
}
w.s(">").elist(elt.contents.link.label).s("</a>")
w.obfuscate = o
2010-11-21 22:04:39 +00:00
case IMAGE:
w.s(`<img src="`).str(elt.contents.link.url).s(`" alt="`)
w.elist(elt.contents.link.label).s(`"`)
2010-11-21 22:04:39 +00:00
if len(elt.contents.link.title) > 0 {
w.s(` title="`).str(elt.contents.link.title).s(`"`)
2010-11-21 22:04:39 +00:00
}
w.s(" />")
case EMPH:
2012-04-26 19:35:18 +00:00
w.inline("<em>", elt)
2010-11-21 22:04:39 +00:00
case STRONG:
2012-04-26 19:35:18 +00:00
w.inline("<strong>", elt)
2010-11-21 22:04:39 +00:00
case LIST:
2012-04-26 19:35:18 +00:00
w.children(elt)
2010-11-21 22:04:39 +00:00
case RAW:
/* Shouldn't occur - these are handled by process_raw_blocks() */
2011-02-18 11:00:25 +00:00
log.Fatalf("RAW")
2010-11-21 22:04:39 +00:00
case H1, H2, H3, H4, H5, H6:
2012-04-26 19:35:18 +00:00
h := "<h" + string('1'+elt.key-H1) + ">" /* assumes H1 ... H6 are in order */
w.sp().inline(h, elt)
2010-11-21 22:04:39 +00:00
case PLAIN:
w.br().children(elt)
2010-11-21 22:04:39 +00:00
case PARA:
w.sp().inline("<p>", elt)
2010-11-21 22:04:39 +00:00
case HRULE:
w.sp().s("<hr />")
2010-11-21 22:04:39 +00:00
case HTMLBLOCK:
w.sp().s(elt.contents.str)
2010-11-21 22:04:39 +00:00
case VERBATIM:
w.sp().s("<pre><code>").str(elt.contents.str).s("</code></pre>")
2010-11-21 22:04:39 +00:00
case BULLETLIST:
2012-04-26 19:35:18 +00:00
w.listBlock("<ul>", elt)
2010-11-21 22:04:39 +00:00
case ORDEREDLIST:
2012-04-26 19:35:18 +00:00
w.listBlock("<ol>", elt)
2010-12-13 18:36:35 +00:00
case DEFINITIONLIST:
2012-04-26 19:35:18 +00:00
w.listBlock("<dl>", elt)
2010-12-13 18:36:35 +00:00
case DEFTITLE:
2012-04-26 19:35:18 +00:00
w.listItem("<dt>", elt)
2010-12-13 18:36:35 +00:00
case DEFDATA:
2012-04-26 19:35:18 +00:00
w.listItem("<dd>", elt)
2010-11-21 22:04:39 +00:00
case LISTITEM:
2012-04-26 19:35:18 +00:00
w.listItem("<li>", elt)
2010-11-21 22:04:39 +00:00
case BLOCKQUOTE:
w.sp().s("<blockquote>\n").skipPadding().children(elt).br().s("</blockquote>")
2010-11-21 22:04:39 +00:00
case REFERENCE:
/* Nonprinting */
case NOTE:
/* if contents.str == 0, then print note; else ignore, since this
* is a note block that has been incorporated into the notes list
*/
if elt.contents.str == "" {
w.endNotes = append(w.endNotes, elt) /* add an endnote to global endnotes list */
2010-11-21 22:04:39 +00:00
w.notenum++
nn := w.notenum
s = fmt.Sprintf(`<a class="noteref" id="fnref%d" href="#fn%d" title="Jump to note %d">[%d]</a>`,
nn, nn, nn, nn)
}
default:
2011-02-18 11:00:25 +00:00
log.Fatalf("htmlOut.elem encountered unknown element key = %d\n", elt.key)
2010-11-21 22:04:39 +00:00
}
if s != "" {
w.s(s)
}
return w
}
func (w *htmlOut) printEndnotes() {
counter := 0
w.s("<hr/>\n<ol id=\"notes\">")
for _, elt := range w.endNotes {
counter++
w.br().s(fmt.Sprintf("<li id=\"fn%d\">\n", counter)).skipPadding()
2012-04-26 19:35:18 +00:00
w.children(elt)
2010-11-21 22:04:39 +00:00
w.s(fmt.Sprintf(" <a href=\"#fnref%d\" title=\"Jump back to reference\">[back]</a>", counter))
w.br().s("</li>")
2010-11-21 22:04:39 +00:00
}
w.br().s("</ol>")
2010-11-21 22:04:39 +00:00
}