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_blockquote(ob *bytes.Buffer, text []byte, opaque interface{}) {
ob.WriteString("\n")
ob.Write(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) && isspace(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 rndr_autolink(ob *bytes.Buffer, link []byte, kind int, opaque interface{}) int {
options := opaque.(*html_renderopts)
if len(link) == 0 {
return 0
}
if options.flags&HTML_SAFELINK != 0 && !is_safe_link(link) && kind != MKDA_EMAIL {
return 0
}
ob.WriteString("")
/*
* Pretty printing: if we get an email address as
* an actual URI, e.g. `mailto:foo@bar.com`, we don't
* want to print the `mailto:` prefix
*/
if bytes.HasPrefix(link, []byte("mailto:")) {
attr_escape(ob, link[7:])
} else {
attr_escape(ob, link)
}
ob.WriteString("")
return 1
}
func rndr_codespan(ob *bytes.Buffer, text []byte, opaque interface{}) int {
ob.WriteString("")
attr_escape(ob, text)
ob.WriteString("")
return 1
}
func rndr_double_emphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
ob.WriteString("")
ob.Write(text)
ob.WriteString("")
return 1
}
func rndr_emphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
ob.WriteString("")
ob.Write(text)
ob.WriteString("")
return 1
}
func rndr_image(ob *bytes.Buffer, link []byte, title []byte, alt []byte, opaque interface{}) int {
options := opaque.(*html_renderopts)
if len(link) == 0 {
return 0
}
ob.WriteString("
0 {
attr_escape(ob, alt)
}
if len(title) > 0 {
ob.WriteString("\" title=\"")
attr_escape(ob, title)
}
ob.WriteByte('"')
ob.WriteString(options.close_tag)
return 1
}
func rndr_linebreak(ob *bytes.Buffer, opaque interface{}) int {
options := opaque.(*html_renderopts)
ob.WriteString("
0 {
ob.Write(link)
}
if len(title) > 0 {
ob.WriteString("\" title=\"")
attr_escape(ob, title)
}
ob.WriteString("\">")
if len(content) > 0 {
ob.Write(content)
}
ob.WriteString("")
return 1
}
func rndr_raw_html_tag(ob *bytes.Buffer, text []byte, opaque interface{}) int {
options := opaque.(*html_renderopts)
if options.flags&HTML_SKIP_HTML != 0 {
return 1
}
if options.flags&HTML_SKIP_STYLE != 0 && is_html_tag(text, "style") {
return 1
}
if options.flags&HTML_SKIP_LINKS != 0 && is_html_tag(text, "a") {
return 1
}
if options.flags&HTML_SKIP_IMAGES != 0 && is_html_tag(text, "img") {
return 1
}
ob.Write(text)
return 1
}
func rndr_triple_emphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
ob.WriteString("")
ob.Write(text)
ob.WriteString("")
return 1
}
func rndr_strikethrough(ob *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
ob.WriteString("")
ob.Write(text)
ob.WriteString("")
return 1
}
func rndr_normal_text(ob *bytes.Buffer, text []byte, opaque interface{}) {
attr_escape(ob, text)
}
func is_html_tag(tag []byte, tagname string) bool {
i := 0
if i < len(tag) && tag[0] != '<' {
return false
}
i++
for i < len(tag) && isspace(tag[i]) {
i++
}
if i < len(tag) && tag[i] == '/' {
i++
}
for i < len(tag) && isspace(tag[i]) {
i++
}
tag_i := i
for ; i < len(tag); i, tag_i = i+1, tag_i+1 {
if tag_i >= len(tagname) {
break
}
if tag[i] != tagname[tag_i] {
return false
}
}
if i == len(tag) {
return false
}
return isspace(tag[i]) || tag[i] == '>'
}
//
//
// Main and public interface
//
//
func main() {
ob := bytes.NewBuffer(nil)
input := ""
// 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. `code this` fool\n"
// input += "\n"
// input += "Click [here](http:google.com)\n"
// input += "\n"
// input += "\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.blockquote = rndr_blockquote
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.autolink = rndr_autolink
rndrer.codespan = rndr_codespan
rndrer.double_emphasis = rndr_double_emphasis
rndrer.emphasis = rndr_emphasis
rndrer.image = rndr_image
rndrer.linebreak = rndr_linebreak
rndrer.link = rndr_link
rndrer.raw_html_tag = rndr_raw_html_tag
rndrer.triple_emphasis = rndr_triple_emphasis
rndrer.strikethrough = rndr_strikethrough
rndrer.normal_text = rndr_normal_text
rndrer.opaque = &html_renderopts{close_tag: ">\n"}
var extensions uint32 = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES | MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_LAX_HTML_BLOCKS | MKDEXT_SPACE_HEADERS
// call the main rendered function
Markdown(ob, ib, rndrer, extensions)
// print the result
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 character-level parsers
markdown_char_ptrs[MD_CHAR_NONE] = nil
markdown_char_ptrs[MD_CHAR_EMPHASIS] = char_emphasis
markdown_char_ptrs[MD_CHAR_CODESPAN] = char_codespan
markdown_char_ptrs[MD_CHAR_LINEBREAK] = char_linebreak
markdown_char_ptrs[MD_CHAR_LINK] = char_link
markdown_char_ptrs[MD_CHAR_LANGLE] = char_langle_tag
markdown_char_ptrs[MD_CHAR_ESCAPE] = char_escape
markdown_char_ptrs[MD_CHAR_ENTITITY] = char_entity
markdown_char_ptrs[MD_CHAR_AUTOLINK] = char_autolink
// 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(nil)
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")
}
}