/* 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" "strings" ) type Writer interface { Write([]byte) (int, error) WriteString(string) (int, error) WriteRune(rune) (int, error) WriteByte(byte) error } type baseWriter struct { Writer padded int } type htmlOut struct { baseWriter obfuscate bool notenum int endNotes []*element /* List of endnotes to print after main content. */ } 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') f.padded = 2 } // 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') } w.padded = 0 } 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 return h } // print a string func (w *htmlOut) s(s string) *htmlOut { w.WriteString(s) return w } /* print string, escaping for HTML * If obfuscate selected, convert characters to hex or decimal entities at random */ func (w *htmlOut) str(s string) *htmlOut { var ws string var i0 = 0 o := w.obfuscate for i, r := range s { switch r { case '&': ws = "&" case '<': ws = "<" case '>': ws = ">" case '"': ws = """ default: if o && r < 128 && r >= 0 { if rand.Intn(2) == 0 { ws = fmt.Sprintf("&#%d;", r) } else { ws = fmt.Sprintf("&#%x;", r) } } else { if i0 == -1 { i0 = i } continue } } if i0 != -1 { w.WriteString(s[i0:i]) i0 = -1 } w.WriteString(ws) } if i0 != -1 { w.WriteString(s[i0:]) } return w } 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("\n" case STR: w.str(elt.contents.str) case ELLIPSIS: s = "…" case EMDASH: s = "—" case ENDASH: s = "–" case APOSTROPHE: s = "’" case SINGLEQUOTED: w.s("‘").children(elt).s("’") case DOUBLEQUOTED: w.s("“").children(elt).s("”") case CODE: w.s("").str(elt.contents.str).s("") case HTML: s = elt.contents.str case LINK: o := w.obfuscate if strings.Index(elt.contents.link.url, "mailto:") == 0 { w.obfuscate = true /* obfuscate mailto: links */ } w.s(` 0 { w.s(` title="`).str(elt.contents.link.title).s(`"`) } w.s(">").elist(elt.contents.link.label).s("") w.obfuscate = o case IMAGE: w.s(``)
		w.elist(elt.contents.link.label).s(` 0 { w.s(` title="`).str(elt.contents.link.title).s(`"`) } w.s(" />") case EMPH: w.inline("", elt) case STRONG: w.inline("", elt) case STRIKE: w.inline("", elt) case LIST: w.children(elt) case RAW: /* Shouldn't occur - these are handled by process_raw_blocks() */ log.Fatalf("RAW") case H1, H2, H3, H4, H5, H6: h := "" /* assumes H1 ... H6 are in order */ w.sp().inline(h, elt) case PLAIN: w.br().children(elt) case PARA: w.sp().inline("

", elt) case HRULE: w.sp().s("


") case HTMLBLOCK: w.sp().s(elt.contents.str) case VERBATIM: w.sp().s("
").str(elt.contents.str).s("
") case BULLETLIST: w.listBlock("
    ", elt) case ORDEREDLIST: w.listBlock("
      ", elt) case DEFINITIONLIST: w.listBlock("
      ", elt) case DEFTITLE: w.listItem("
      ", elt) case DEFDATA: w.listItem("
      ", elt) case LISTITEM: w.listItem("
    1. ", elt) case BLOCKQUOTE: w.sp().s("
      \n").skipPadding().children(elt).br().s("
      ") 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 */ w.notenum++ nn := w.notenum s = fmt.Sprintf(`[%d]`, nn, nn, nn, nn) } default: log.Fatalf("htmlOut.elem encountered unknown element key = %d\n", elt.key) } if s != "" { w.s(s) } return w } func (w *htmlOut) printEndnotes() { extraNewline := func() { // add an extra newline to maintain // compatibility with the C version. w.padded-- } counter := 0 w.s("
      \n
        ") for _, elt := range w.endNotes { counter++ extraNewline() w.br().s(fmt.Sprintf("
      1. \n", counter)).skipPadding() w.children(elt) w.s(fmt.Sprintf(" [back]", counter)) w.br().s("
      2. ") } extraNewline() w.br().s("
      ") }