package main import ( "bytes" "fmt" "html" "sort" "unicode" ) const ( MKDA_NOT_AUTOLINK = iota MKDA_NORMAL MKDA_EMAIL ) const ( MKDEXT_NO_INTRA_EMPHASIS = 1 << iota MKDEXT_TABLES MKDEXT_FENCED_CODE MKDEXT_AUTOLINK MKDEXT_STRIKETHROUGH MKDEXT_LAX_HTML_BLOCKS MKDEXT_SPACE_HEADERS ) const ( _ = iota MKD_LIST_ORDERED MKD_LI_BLOCK //
0 {
ob.WriteByte(' ')
}
attr_escape(ob, []byte(lang[org:]))
}
}
ob.WriteString("\">")
} else {
ob.WriteString("")
}
if len(text) > 0 {
attr_escape(ob, text)
}
ob.WriteString("
\n")
}
func rndr_table(ob *bytes.Buffer, header []byte, body []byte, opaque interface{}) {
if ob.Len() > 0 {
ob.WriteByte('\n')
}
ob.WriteString("\n")
ob.Write(header)
ob.WriteString("\n\n")
ob.Write(body)
ob.WriteString("\n
")
}
func rndr_tablerow(ob *bytes.Buffer, text []byte, opaque interface{}) {
if ob.Len() > 0 {
ob.WriteByte('\n')
}
ob.WriteString("\n")
ob.Write(text)
ob.WriteString("\n ")
}
func rndr_tablecell(ob *bytes.Buffer, text []byte, align int, opaque interface{}) {
if ob.Len() > 0 {
ob.WriteByte('\n')
}
switch align {
case MKD_TABLE_ALIGN_L:
ob.WriteString("")
case MKD_TABLE_ALIGN_R:
ob.WriteString(" ")
case MKD_TABLE_ALIGN_CENTER:
ob.WriteString(" ")
default:
ob.WriteString(" ")
}
ob.Write(text)
ob.WriteString(" ")
}
func rndr_list(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) {
if ob.Len() > 0 {
ob.WriteByte('\n')
}
if flags&MKD_LIST_ORDERED != 0 {
ob.WriteString("\n")
} else {
ob.WriteString("\n")
}
ob.Write(text)
if flags&MKD_LIST_ORDERED != 0 {
ob.WriteString("
\n")
} else {
ob.WriteString("\n")
}
}
func rndr_listitem(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) {
ob.WriteString("")
size := len(text)
for size > 0 && text[size-1] == '\n' {
size--
}
ob.Write(text[:size])
ob.WriteString(" \n")
}
func rndr_paragraph(ob *bytes.Buffer, text []byte, opaque interface{}) {
options := opaque.(*html_renderopts)
i := 0
if ob.Len() > 0 {
ob.WriteByte('\n')
}
if len(text) == 0 {
return
}
for i < len(text) && unicode.IsSpace(int(text[i])) {
i++
}
if i == len(text) {
return
}
ob.WriteString("")
if options.flags&HTML_HARD_WRAP != 0 {
for i < len(text) {
org := i
for i < len(text) && text[i] != '\n' {
i++
}
if i > org {
ob.Write(text[org:i])
}
if i >= len(text) {
break
}
ob.WriteString("
")
ob.WriteString(options.close_tag)
i++
}
} else {
ob.Write(text[i:])
}
ob.WriteString("
\n")
}
func main() {
ob := bytes.NewBuffer(nil)
input := "##Header##\n"
input += "\n"
input += "----------\n"
input += "\n"
input += "Underlined header\n"
input += "-----------------\n"
input += "\n"
input += "Some block html\n"
input += "
\n"
input += "\n"
input += "Score | Grade\n"
input += "------|------\n"
input += "94 | A\n"
input += "85 | B\n"
input += "74 | C\n"
input += "65 | D\n"
input += "\n"
input += "``` go\n"
input += "func fib(n int) int {\n"
input += " if n <= 1 {\n"
input += " return n\n"
input += " }\n"
input += " return n * fib(n-1)\n"
input += "}\n"
input += "```\n"
input += "\n"
input += "> A blockquote\n"
input += "> or something like that\n"
input += "> With a table | of two columns\n"
input += "> -------------|---------------\n"
input += "> key | value \n"
input += "\n"
input += "\n"
input += "Some **bold** Some *italic* and [a link][1] \n"
input += "\n"
input += "A little code sample\n"
input += "\n"
input += " \n"
input += " Web Page Title \n"
input += " \n"
input += "\n"
input += "A picture\n"
input += "\n"
input += "![alt text][2]\n"
input += "\n"
input += "A list\n"
input += "\n"
input += "- apples\n"
input += "- oranges\n"
input += "- eggs\n"
input += "\n"
input += "A numbered list\n"
input += "\n"
input += "1. a\n"
input += "2. b\n"
input += "3. c\n"
input += "\n"
input += "A little quote\n"
input += "\n"
input += "> It is now time for all good men to come to the aid of their country. \n"
input += "\n"
input += "A final paragraph.\n"
input += "\n"
input += " [1]: http://www.google.com\n"
input += " [2]: http://www.google.com/intl/en_ALL/images/logo.gif\n"
ib := []byte(input)
rndrer := new(mkd_renderer)
rndrer.blockcode = rndr_blockcode
rndrer.blockhtml = rndr_raw_block
rndrer.header = rndr_header
rndrer.hrule = rndr_hrule
rndrer.list = rndr_list
rndrer.listitem = rndr_listitem
rndrer.paragraph = rndr_paragraph
rndrer.table = rndr_table
rndrer.table_row = rndr_tablerow
rndrer.table_cell = rndr_tablecell
rndrer.opaque = &html_renderopts{close_tag: " />"}
var extensions uint32 = MKDEXT_FENCED_CODE | MKDEXT_TABLES
Markdown(ob, ib, rndrer, extensions)
fmt.Print(ob.String())
}
func expand_tabs(ob *bytes.Buffer, line []byte) {
i, tab := 0, 0
for i < len(line) {
org := i
for i < len(line) && line[i] != '\t' {
i++
tab++
}
if i > org {
ob.Write(line[org:i])
}
if i >= len(line) {
break
}
for {
ob.WriteByte(' ')
tab++
if tab%4 == 0 {
break
}
}
i++
}
}
func Markdown(ob *bytes.Buffer, ib []byte, rndrer *mkd_renderer, extensions uint32) {
// no point in parsing if we can't render
if rndrer == nil {
return
}
// fill in the render structure
rndr := new(render)
rndr.mk = rndrer
rndr.ext_flags = extensions
rndr.max_nesting = 16
if rndr.mk.emphasis != nil || rndr.mk.double_emphasis != nil || rndr.mk.triple_emphasis != nil {
rndr.active_char['*'] = MD_CHAR_EMPHASIS
rndr.active_char['_'] = MD_CHAR_EMPHASIS
if extensions&MKDEXT_STRIKETHROUGH != 0 {
rndr.active_char['~'] = MD_CHAR_EMPHASIS
}
}
if rndr.mk.codespan != nil {
rndr.active_char['`'] = MD_CHAR_CODESPAN
}
if rndr.mk.linebreak != nil {
rndr.active_char['\n'] = MD_CHAR_LINEBREAK
}
if rndr.mk.image != nil || rndr.mk.link != nil {
rndr.active_char['['] = MD_CHAR_LINK
}
rndr.active_char['<'] = MD_CHAR_LANGLE
rndr.active_char['\\'] = MD_CHAR_ESCAPE
rndr.active_char['&'] = MD_CHAR_ENTITITY
if extensions&MKDEXT_AUTOLINK != 0 {
rndr.active_char['h'] = MD_CHAR_AUTOLINK // http, https
rndr.active_char['H'] = MD_CHAR_AUTOLINK
rndr.active_char['f'] = MD_CHAR_AUTOLINK // ftp
rndr.active_char['F'] = MD_CHAR_AUTOLINK
rndr.active_char['m'] = MD_CHAR_AUTOLINK // mailto
rndr.active_char['M'] = MD_CHAR_AUTOLINK
}
// first pass: look for references, copying everything else
text := bytes.NewBuffer(make([]byte, len(ib)))
beg, end := 0, 0
for beg < len(ib) { // iterate over lines
if is_ref(ib, beg, &end, rndr) {
beg = end
} else { // skip to the next line
end = beg
for end < len(ib) && ib[end] != '\n' && ib[end] != '\r' {
end++
}
// add the line body if present
if end > beg {
expand_tabs(text, ib[beg:end])
}
for end < len(ib) && (ib[end] == '\n' || ib[end] == '\r') {
// add one \n per newline
if ib[end] == '\n' || (end+1 < len(ib) && ib[end+1] != '\n') {
text.WriteByte('\n')
}
end++
}
beg = end
}
}
// sort the reference array
if len(rndr.refs) > 1 {
sort.Sort(rndr.refs)
}
// second pass: actual rendering
if rndr.mk.doc_header != nil {
rndr.mk.doc_header(ob, rndr.mk.opaque)
}
if text.Len() > 0 {
// add a final newline if not already present
finalchar := text.Bytes()[text.Len()-1]
if finalchar != '\n' && finalchar != '\r' {
text.WriteByte('\n')
}
parse_block(ob, rndr, text.Bytes())
}
if rndr.mk.doc_footer != nil {
rndr.mk.doc_footer(ob, rndr.mk.opaque)
}
if rndr.nesting != 0 {
panic("Nesting level did not end at zero")
}
}