diff --git a/markdown.go b/markdown.go index 29b743d..58b53fb 100644 --- a/markdown.go +++ b/markdown.go @@ -1,8 +1,10 @@ package main import ( - "fmt" "bytes" + "fmt" + "html" + "unicode" ) const ( @@ -21,6 +23,19 @@ const ( MKDEXT_SPACE_HEADERS ) +const ( + _ = iota + MKD_LIST_ORDERED + MKD_LI_BLOCK //
  • containing block data + MKD_LI_END = 8 +) + +const ( + MKD_TABLE_ALIGN_L = 1 << iota + MKD_TABLE_ALIGN_R + MKD_TABLE_ALIGN_CENTER = (MKD_TABLE_ALIGN_L | MKD_TABLE_ALIGN_R) +) + var block_tags = map[string]int{ "p": 1, // 0 "dl": 2, @@ -46,15 +61,27 @@ var block_tags = map[string]int{ "blockquote": 10, } +// functions for rendering parsed data type mkd_renderer struct { - blockhtml func(ob *bytes.Buffer, text []byte, opaque interface{}) - header func(ob *bytes.Buffer, text []byte, level int, opaque interface{}) - hrule func(ob *bytes.Buffer, opaque interface{}) - opaque interface{} + // block-level callbacks---nil skips the block + blockcode func(ob *bytes.Buffer, text []byte, lang string, opaque interface{}) + blockquote func(ob *bytes.Buffer, text []byte, opaque interface{}) + blockhtml func(ob *bytes.Buffer, text []byte, opaque interface{}) + header func(ob *bytes.Buffer, text []byte, level int, opaque interface{}) + hrule func(ob *bytes.Buffer, opaque interface{}) + list func(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) + listitem func(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) + paragraph func(ob *bytes.Buffer, text []byte, opaque interface{}) + table func(ob *bytes.Buffer, header []byte, body []byte, opaque interface{}) + table_row func(ob *bytes.Buffer, text []byte, opaque interface{}) + table_cell func(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) + + // user data---passed back to every callback + opaque interface{} } type render struct { - maker mkd_renderer + mk mkd_renderer ext_flags uint32 // ... } @@ -64,6 +91,64 @@ func parse_inline(work *bytes.Buffer, rndr *render, data []byte) { work.Write(data) } +// parse block-level data +func parse_block(ob *bytes.Buffer, rndr *render, data []byte) { + // TODO: quit if max_nesting exceeded + + for len(data) > 0 { + if is_atxheader(rndr, data) { + data = data[parse_atxheader(ob, rndr, data):] + continue + } + if data[0] == '<' && rndr.mk.blockhtml != nil { + if i := parse_htmlblock(ob, rndr, data, true); i > 0 { + data = data[i:] + continue + } + } + if i := is_empty(data); i > 0 { + data = data[i:] + continue + } + if is_hrule(data) { + if rndr.mk.hrule != nil { + rndr.mk.hrule(ob, rndr.mk.opaque) + } + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + } + data = data[i:] + continue + } + if rndr.ext_flags&MKDEXT_FENCED_CODE != 0 { + if i := parse_fencedcode(ob, rndr, data); i > 0 { + data = data[i:] + continue + } + } + if rndr.ext_flags&MKDEXT_TABLES != 0 { + if i := parse_table(ob, rndr, data); i > 0 { + data = data[i:] + continue + } + } + if prefix_quote(data) > 0 { + data = data[parse_blockquote(ob, rndr, data):] + continue + } + if prefix_code(data) > 0 { + data = data[parse_blockcode(ob, rndr, data):] + continue + } + if prefix_uli(data) > 0 { + data = data[parse_list(ob, rndr, data, 0):] + continue + } + + data = data[1:] + } +} + func is_atxheader(rndr *render, data []byte) bool { if data[0] != '#' { return false @@ -81,6 +166,181 @@ func is_atxheader(rndr *render, data []byte) bool { return true } +func parse_atxheader(ob *bytes.Buffer, rndr *render, data []byte) int { + level := 0 + for level < len(data) && level < 6 && data[level] == '#' { + level++ + } + i, end := 0, 0 + for i = level; i < len(data) && (data[i] == ' ' || data[i] == '\t'); i++ { + } + for end = i; end < len(data) && data[end] != '\n'; end++ { + } + skip := end + for end > 0 && data[end-1] == '#' { + end-- + } + for end > 0 && (data[end-1] == ' ' || data[end-1] == '\t') { + end-- + } + if end > i { + work := bytes.NewBuffer(nil) + parse_inline(work, rndr, data[i:end]) + if rndr.mk.header != nil { + rndr.mk.header(ob, work.Bytes(), level, rndr.mk.opaque) + } + } + return skip +} + +func parse_htmlblock(ob *bytes.Buffer, rndr *render, data []byte, do_render bool) int { + var i, j int + + // identification of the opening tag + if len(data) < 2 || data[0] != '<' { + return 0 + } + curtag, tagfound := find_block_tag(data[1:]) + + // handling of special cases + if !tagfound { + + // HTML comment, laxist form + if len(data) > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-' { + i = 5 + + for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') { + i++ + } + i++ + + if i < len(data) { + j = is_empty(data[i:]) + } + + if j > 0 { + size := i + j + if do_render && rndr.mk.blockhtml != nil { + rndr.mk.blockhtml(ob, data[:size], rndr.mk.opaque) + } + return size + } + } + + // HR, which is the only self-closing block tag considered + if len(data) > 4 && (data[i] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R') { + i = 3 + for i < len(data) && data[i] != '>' { + i++ + } + + if i+1 < len(data) { + i++ + j = is_empty(data[i:]) + if j > 0 { + size := i + j + if do_render && rndr.mk.blockhtml != nil { + rndr.mk.blockhtml(ob, data[:size], rndr.mk.opaque) + } + return size + } + } + } + + // no special case recognized + return 0 + } + + // looking for an unindented matching closing tag + // followed by a blank line + i = 1 + found := false + + // if not found, trying a second pass looking for indented match + // but not if tag is "ins" or "del" (following original Markdown.pl) + if curtag != "ins" && curtag != "del" { + i = 1 + for i < len(data) { + i++ + for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { + i++ + } + + if i+2+len(curtag) >= len(data) { + break + } + + j = htmlblock_end(curtag, rndr, data[i-1:]) + + if j > 0 { + i += j - 1 + found = true + break + } + } + } + + if !found { + return 0 + } + + // the end of the block has been found + if do_render && rndr.mk.blockhtml != nil { + rndr.mk.blockhtml(ob, data[:i], rndr.mk.opaque) + } + + return i +} + +func find_block_tag(data []byte) (string, bool) { + i := 0 + for i < len(data) && ((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= 'a' && data[i] <= 'z')) { + i++ + } + if i >= len(data) { + return "", false + } + key := string(data[:i]) + if _, ok := block_tags[key]; ok { + return key, true + } + return "", false +} + +func htmlblock_end(tag string, rndr *render, data []byte) int { + // assuming data[0] == '<' && data[1] == '/' already tested + + // checking tag is a match + if len(tag)+3 >= len(data) || bytes.Compare(data[2:2+len(tag)], []byte(tag)) != 0 || data[len(tag)+2] != '>' { + return 0 + } + + // checking white lines + i := len(tag) + 3 + w := 0 + if i < len(data) { + if w = is_empty(data[i:]); w == 0 { + return 0 // non-blank after tag + } + } + i += w + w = 0 + + if rndr.ext_flags&MKDEXT_LAX_HTML_BLOCKS != 0 { + if i < len(data) { + w = is_empty(data[i:]) + } + } else { + if i < len(data) { + if w = is_empty(data[i:]); w == 0 { + return 0 // non-blank line after tag line + } + } + } + + return i + w +} + func is_empty(data []byte) int { var i int for i = 0; i < len(data) && data[i] != '\n'; i++ { @@ -128,239 +388,489 @@ func is_hrule(data []byte) bool { return n >= 3 } -func find_block_tag(data []byte) (string, bool) { - i := 0 - for i < len(data) && ((data[i] >= '0' && data[i] <= '9') || (data[i] >= 'A' && data[i] <= 'Z') || (data[i] >= 'a' && data[i] <= 'z')) { +func is_codefence(data []byte, syntax **string) int { + i, n := 0, 0 + + // skipping initial spaces + if len(data) < 3 { + return 0 + } + if data[0] == ' ' { + i++ + if data[1] == ' ' { + i++ + if data[2] == ' ' { + i++ + } + } + } + + // looking at the hrule char + if i+2 >= len(data) || !(data[i] == '~' || data[i] == '`') { + return 0 + } + + c := data[i] + + // the whole line must be the char or whitespace + for i < len(data) && data[i] == c { + n++ i++ } - if i >= len(data) { - return "", false - } - key := string(data[:i]) - if _, ok := block_tags[key]; ok { - return key, true - } - return "", false -} -func parse_atxheader(ob *bytes.Buffer, rndr *render, data []byte) int { - level := 0 - for level < len(data) && level < 6 && data[level] == '#' { - level++ - } - i, end := 0, 0 - for i = level; i < len(data) && (data[i] == ' ' || data[i] == '\t'); i++ { - } - for end = i; end < len(data) && data[end] != '\n'; end++ { - } - skip := end - for end > 0 && data[end-1] == '#' { - end-- - } - for end > 0 && (data[end-1] == ' ' || data[end-1] == '\t') { - end-- - } - if end > i { - work := new(bytes.Buffer) - parse_inline(work, rndr, data[i:end]) - if rndr.maker.header != nil { - rndr.maker.header(ob, work.Bytes(), level, rndr.maker.opaque) - } - } - return skip -} - -func htmlblock_end(tag string, rndr *render, data []byte) int { - // assuming data[0] == '<' && data[1] == '/' already tested - - // checking tag is a match - if len(tag)+3 >= len(data) || bytes.Compare(data[2:2+len(tag)], []byte(tag)) != 0 || data[len(tag)+2] != '>' { + if n < 3 { return 0 } - // checking white lines - i := len(tag) + 3 - w := 0 - if i < len(data) { - if w = is_empty(data[i:]); w == 0 { - return 0 // non-blank after tag - } - } - i += w - w = 0 + if syntax != nil { + syn := 0 - if rndr.ext_flags&MKDEXT_LAX_HTML_BLOCKS != 0 { - if i < len(data) { - w = is_empty(data[i:]) - } - } else { - if i < len(data) { - if w = is_empty(data[i:]); w == 0 { - return 0 // non-blank line after tag line - } - } - } - - return i + w -} - -func parse_htmlblock(ob *bytes.Buffer, rndr *render, data []byte, do_render bool) int { - var i, j int - - // identification of the opening tag - if len(data) < 2 || data[0] != '<' { - return 0 - } - curtag, tagfound := find_block_tag(data[1:]) - - // handling of special cases - if !tagfound { - - // HTML comment, laxist form - if len(data) > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-' { - i = 5 - - for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') { - i++ - } + for i < len(data) && (data[i] == ' ' || data[i] == '\t') { i++ - - if i < len(data) { - j = is_empty(data[i:]) - } - - if j > 0 { - size := i + j - if do_render && rndr.maker.blockhtml != nil { - rndr.maker.blockhtml(ob, data[:size], rndr.maker.opaque) - } - return size - } } - // HR, which is the only self-closing block tag considered - if len(data) > 4 && (data[i] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R') { - i = 3 - for i < len(data) && data[i] != '>' { + syntax_start := i + + if i < len(data) && data[i] == '{' { + i++ + syntax_start++ + + for i < len(data) && data[i] != '}' && data[i] != '\n' { + syn++ i++ } - if i+1 < len(data) { + if i == len(data) || data[i] != '}' { + return 0 + } + + // string all whitespace at the beginning and the end + // of the {} block + for syn > 0 && unicode.IsSpace(int(data[syntax_start])) { + syntax_start++ + syn-- + } + + for syn > 0 && unicode.IsSpace(int(data[syntax_start+syn-1])) { + syn-- + } + + i++ + } else { + for i < len(data) && !unicode.IsSpace(int(data[i])) { + syn++ i++ - j = is_empty(data[i:]) - if j > 0 { - size := i + j - if do_render && rndr.maker.blockhtml != nil { - rndr.maker.blockhtml(ob, data[:size], rndr.maker.opaque) - } - return size - } } } - // no special case recognized + language := string(data[syntax_start : syntax_start+syn]) + *syntax = &language + } + + for i < len(data) && data[i] != '\n' { + if !unicode.IsSpace(int(data[i])) { + return 0 + } + i++ + } + + return i + 1 +} + +func parse_fencedcode(ob *bytes.Buffer, rndr *render, data []byte) int { + var lang *string + beg := is_codefence(data, &lang) + if beg == 0 { return 0 } - // looking for an unindented matching closing tag - // followed by a blank line - i = 1 - found := false + work := bytes.NewBuffer(nil) + + for beg < len(data) { + fence_end := is_codefence(data[beg:], nil) + if fence_end != 0 { + beg += fence_end + break + } + + var end int + for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ { + } + + if beg < end { + // verbatim copy to the working buffer, escaping entities + if is_empty(data[beg:]) > 0 { + work.WriteByte('\n') + } else { + work.Write(data[beg:end]) + } + } + beg = end + } + + if work.Len() > 0 && work.Bytes()[work.Len()-1] != '\n' { + work.WriteByte('\n') + } + + if rndr.mk.blockcode != nil { + syntax := "" + if lang != nil { + syntax = *lang + } + + rndr.mk.blockcode(ob, work.Bytes(), syntax, rndr.mk.opaque) + } + + return beg +} + +func parse_table(ob *bytes.Buffer, rndr *render, data []byte) int { + header_work := bytes.NewBuffer(nil) + i, columns, col_data := parse_table_header(header_work, rndr, data) + if i > 0 { + body_work := bytes.NewBuffer(nil) - // if not found, trying a second pass looking for indented match - // but not if tag is "ins" or "del" (folloing original Markdown.pl) - if curtag != "ins" && curtag != "del" { - i = 1 for i < len(data) { + pipes, row_start := 0, i + for ; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' { + pipes++ + } + } + + if pipes == 0 || i == len(data) { + i = row_start + break + } + + parse_table_row(body_work, rndr, data[row_start:i], columns, col_data) i++ - for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { - i++ - } - - if i+2+len(curtag) >= len(data) { - break - } - - j = htmlblock_end(curtag, rndr, data[i-1:]) - - if j > 0 { - i += j - 1 - found = true - break - } } - } - if !found { - return 0 - } - - // the end of the block has been found - if do_render && rndr.maker.blockhtml != nil { - rndr.maker.blockhtml(ob, data[:i], rndr.maker.opaque) + if rndr.mk.table != nil { + rndr.mk.table(ob, header_work.Bytes(), body_work.Bytes(), rndr.mk.opaque) + } } return i } -func parse_block(ob *bytes.Buffer, rndr *render, data []byte) { - // TODO: quit if max_nesting exceeded - - for len(data) > 0 { - if is_atxheader(rndr, data) { - data = data[parse_atxheader(ob, rndr, data):] - continue +func parse_table_header(ob *bytes.Buffer, rndr *render, data []byte) (size int, columns int, column_data []int) { + i, pipes := 0, 0 + column_data = []int{} + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' { + pipes++ } - if data[0] == '<' && rndr.maker.blockhtml != nil { - if i := parse_htmlblock(ob, rndr, data, true); i > 0 { - data = data[i:] - continue + } + + if i == len(data) || pipes == 0 { + return 0, 0, column_data + } + + header_end := i + + if data[0] == '|' { + pipes-- + } + + if i > 2 && data[i-1] == '|' { + pipes-- + } + + columns = pipes + 1 + column_data = make([]int, columns) + + // parse the header underline + i++ + if i < len(data) && data[i] == '|' { + i++ + } + + under_end := i + for under_end < len(data) && data[under_end] != '\n' { + under_end++ + } + + col := 0 + for ; col < columns && i < under_end; col++ { + dashes := 0 + + for i < under_end && (data[i] == ' ' || data[i] == '\t') { + i++ + } + + if data[i] == ':' { + i++ + column_data[col] |= MKD_TABLE_ALIGN_L + dashes++ + } + + for i < under_end && data[i] == '-' { + i++ + dashes++ + } + + if i < under_end && data[i] == ':' { + i++ + column_data[col] |= MKD_TABLE_ALIGN_R + dashes++ + } + + for i < under_end && (data[i] == ' ' || data[i] == '\t') { + i++ + } + + if i < under_end && data[i] != '|' { + break + } + + if dashes < 3 { + break + } + + i++ + } + + if col < columns { + return 0, 0, column_data + } + + parse_table_row(ob, rndr, data[:header_end], columns, column_data) + size = under_end + 1 + return +} + +func parse_table_row(ob *bytes.Buffer, rndr *render, data []byte, columns int, col_data []int) { + i, col := 0, 0 + row_work := bytes.NewBuffer(nil) + + if i < len(data) && data[i] == '|' { + i++ + } + + for col = 0; col < columns && i < len(data); col++ { + for i < len(data) && unicode.IsSpace(int(data[i])) { + i++ + } + + cell_start := i + + for i < len(data) && data[i] != '|' { + i++ + } + + cell_end := i - 1 + + for cell_end > cell_start && unicode.IsSpace(int(data[cell_end])) { + cell_end-- + } + + cell_work := bytes.NewBuffer(nil) + parse_inline(cell_work, rndr, data[cell_start:cell_end+1]) + + if rndr.mk.table_cell != nil { + cdata := 0 + if col < len(col_data) { + cdata = col_data[col] + } + rndr.mk.table_cell(row_work, cell_work.Bytes(), cdata, rndr.mk.opaque) + } + + i++ + } + + for ; col < columns; col++ { + empty_cell := []byte{} + if rndr.mk.table_cell != nil { + cdata := 0 + if col < len(col_data) { + cdata = col_data[col] + } + rndr.mk.table_cell(row_work, empty_cell, cdata, rndr.mk.opaque) + } + } + + if rndr.mk.table_row != nil { + rndr.mk.table_row(ob, row_work.Bytes(), rndr.mk.opaque) + } +} + +// returns blockquote prefix length +func prefix_quote(data []byte) int { + i := 0 + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + if i < len(data) && data[i] == '>' { + if i+1 < len(data) && (data[i+1] == ' ' || data[i+1] == '\t') { + return i + 2 + } + return i + 1 + } + return 0 +} + +// handles parsing of a blockquote fragment +func parse_blockquote(ob *bytes.Buffer, rndr *render, data []byte) int { + out := bytes.NewBuffer(nil) + work := bytes.NewBuffer(nil) + beg, end := 0, 0 + for beg < len(data) { + for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ { + } + + if pre := prefix_quote(data[beg:]); pre > 0 { + beg += pre // skipping prefix + } else { + // empty line followed by non-quote line + if is_empty(data[beg:]) > 0 && (end >= len(data) || (prefix_quote(data[end:]) == 0 && is_empty(data[end:]) == 0)) { + break } } - if i := is_empty(data); i > 0 { - data = data[i:] - continue + + if beg < end { // copy into the in-place working buffer + work.Write(data[beg:end]) } - if is_hrule(data) { - if rndr.maker.hrule != nil { - rndr.maker.hrule(ob, rndr.maker.opaque) - } - var i int - for i = 0; i < len(data) && data[i] != '\n'; i++ {} - data = data[i:] - } - - data = data[1:] - } -} - -func Ups_markdown(ob *bytes.Buffer, ib []byte, rndrer *mkd_renderer, extensions uint32) { - - /* filling the render structure */ - if rndrer == nil { - return + beg = end } - rndr := &render{*rndrer, 0} - - parse_block(ob, rndr, ib) + parse_block(out, rndr, work.Bytes()) + if rndr.mk.blockquote != nil { + rndr.mk.blockquote(ob, out.Bytes(), rndr.mk.opaque) + } + return end } -func main() { - ob := new(bytes.Buffer) - input := "### Header 3\n-----\n# Header 1 #\n\n" - ib := bytes.NewBufferString(input).Bytes() - rndrer := new(mkd_renderer) - rndrer.blockhtml = rndr_raw_block - rndrer.header = rndr_header - rndrer.hrule = rndr_hrule - rndrer.opaque = &html_renderopts{close_tag:" />"} - var extensions uint32 - extensions = 0 - Ups_markdown(ob, ib, rndrer, extensions) - fmt.Print(ob.String()) +// returns prefix length for block code +func prefix_code(data []byte) int { + if len(data) > 0 && data[0] == '\t' { + return 1 + } + if len(data) > 3 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { + return 4 + } + return 0 } +func parse_blockcode(ob *bytes.Buffer, rndr *render, data []byte) int { + work := bytes.NewBuffer(nil) + + beg, end := 0, 0 + for beg < len(data) { + for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ { + } + + chunk := data[beg:end] + if pre := prefix_code(chunk); pre > 0 { + beg += pre + } else { + if is_empty(chunk) == 0 { + // non-empty non-prefixed line breaks the pre + break + } + } + + if beg < end { + // verbatim copy to the working buffer, escaping entities + if is_empty(chunk) > 0 { + work.WriteByte('\n') + } else { + work.Write(chunk) + } + } + beg = end + } + + // trim all the \n off the end of work + workbytes := work.Bytes() + n := 0 + for len(workbytes) > n && workbytes[len(workbytes)-n-1] == '\n' { + n++ + } + if n > 0 { + work = bytes.NewBuffer(workbytes[:len(workbytes)-n]) + } + + work.WriteByte('\n') + + if rndr.mk.blockcode != nil { + rndr.mk.blockcode(ob, work.Bytes(), "", rndr.mk.opaque) + } + + return beg +} + +// returns unordered list item prefix +func prefix_uli(data []byte) int { + i := 0 + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + if i+1 >= len(data) || (data[i] != '*' && data[i] != '+' && data[i] != '-') || (data[i+1] != ' ' && data[i+1] != '\t') { + return 0 + } + return i + 2 +} + +// returns ordered list item prefix +func prefix_oli(data []byte) int { + i := 0 + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + if i >= len(data) || data[i] < '0' || data[i] > '9' { + return 0 + } + for i < len(data) && data[i] >= '0' && data[i] <= '9' { + i++ + } + if i+1 >= len(data) || data[i] != '.' || (data[i+1] != ' ' && data[i+1] != '\t') { + return 0 + } + return i + 2 +} + +// parsing ordered or unordered list block +func parse_list(ob *bytes.Buffer, rndr *render, data []byte, flags int) int { + work := bytes.NewBuffer(nil) + + i, j, flags := 0, 0, 0 + for i < len(data) { + j, flags = parse_listitem(work, rndr, data[i:], flags) + i += j + + if j == 0 || flags&MKD_LI_END != 0 { + break + } + } + + if rndr.mk.list != nil { + rndr.mk.list(ob, work.Bytes(), flags, rndr.mk.opaque) + } + return i +} + +func parse_listitem(ob *bytes.Buffer, rndr *render, data []byte, flags_in int) (size int, flags int) { + size, flags = 0, flags_in + + // keeping book of the first indentation prefix + beg, end, pre, sublist, orgpre, i := 0, 0, 0, 0, 0 + + for orgpre < 3 && orgpre < len(data) && data[orgpre] == ' ' { + orgpre++ + } + + // TODO: stopped here + return +} + + +// +// +// HTML rendering +// +// const ( HTML_SKIP_HTML = 1 << iota @@ -384,6 +894,10 @@ type html_renderopts struct { close_tag string } +func attr_escape(ob *bytes.Buffer, src []byte) { + ob.WriteString(html.EscapeString(string(src))) +} + func rndr_header(ob *bytes.Buffer, text []byte, level int, opaque interface{}) { options := opaque.(*html_renderopts) @@ -398,9 +912,7 @@ func rndr_header(ob *bytes.Buffer, text []byte, level int, opaque interface{}) { ob.WriteString(fmt.Sprintf("", level)) } - if len(text) > 0 { - ob.Write(text) - } + ob.Write(text) ob.WriteString(fmt.Sprintf("\n", level)) } @@ -426,9 +938,185 @@ func rndr_raw_block(ob *bytes.Buffer, text []byte, opaque interface{}) { func rndr_hrule(ob *bytes.Buffer, opaque interface{}) { options := opaque.(*html_renderopts) - if ob.Len() > 0 { - ob.WriteByte('\n') - } - ob.WriteString(" 0 { + ob.WriteByte('\n') + } + ob.WriteString(" 0 { + ob.WriteByte('\n') + } + + if lang != "" { + ob.WriteString("
     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 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.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 + Ups_markdown(ob, ib, rndrer, extensions) + fmt.Print(ob.String()) +} + +func Ups_markdown(ob *bytes.Buffer, ib []byte, rndrer *mkd_renderer, extensions uint32) { + + /* filling the render structure */ + if rndrer == nil { + return + } + + rndr := &render{*rndrer, extensions} + + parse_block(ob, rndr, ib) }