diff --git a/markdown.go b/markdown.go index 0403e3c..3824a01 100644 --- a/markdown.go +++ b/markdown.go @@ -1,9 +1,16 @@ +// +// Black Friday Markdown Processor +// Ported to Go from http://github.com/tanoku/upskirt +// by Russ Ross +// + package main import ( "bytes" "fmt" - "html" + "io/ioutil" + "os" "sort" "unicode" ) @@ -651,7 +658,7 @@ func char_link(ob *bytes.Buffer, rndr *render, data []byte, offset int) int { } // find the link_ref with matching id - index := sort.Search(len(rndr.refs), func(i int) bool { + index := sortDotSearch(len(rndr.refs), func(i int) bool { return !byteslice_less(rndr.refs[i].id, id) }) if index >= len(rndr.refs) || !bytes.Equal(rndr.refs[index].id, id) { @@ -687,7 +694,7 @@ func char_link(ob *bytes.Buffer, rndr *render, data []byte, offset int) int { } // find the link_ref with matching id - index := sort.Search(len(rndr.refs), func(i int) bool { + index := sortDotSearch(len(rndr.refs), func(i int) bool { return !byteslice_less(rndr.refs[i].id, id) }) if index >= len(rndr.refs) || !bytes.Equal(rndr.refs[index].id, id) { @@ -751,7 +758,7 @@ func char_langle_tag(ob *bytes.Buffer, rndr *render, data []byte, offset int) in switch { case rndr.mk.autolink != nil && altype != MKDA_NOT_AUTOLINK: u_link := bytes.NewBuffer(nil) - unscape_text(u_link, data[1:end-2]) + unscape_text(u_link, data[1:end+1-2]) ret = rndr.mk.autolink(ob, u_link.Bytes(), altype, rndr.mk.opaque) case rndr.mk.raw_html_tag != nil: ret = rndr.mk.raw_html_tag(ob, data[:end], rndr.mk.opaque) @@ -931,6 +938,25 @@ func ispunct(c byte) bool { return false } +// this is sort.Search, reproduced here because an older +// version of the library had a bug +func sortDotSearch(n int, f func(int) bool) int { + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. + i, j := 0, n + for i < j { + h := i + (j-i)/2 // avoid overflow when computing h + // i ≤ h < j + if !f(h) { + i = h + 1 // preserves f(i-1) == false + } else { + j = h // preserves f(j) == true + } + } + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. + return i +} + func isspace(c byte) bool { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v' } @@ -1446,7 +1472,7 @@ func parse_htmlblock(ob *bytes.Buffer, rndr *render, data []byte, do_render bool } // 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') { + if len(data) > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R') { i = 3 for i < len(data) && data[i] != '>' { i++ @@ -1979,11 +2005,10 @@ func parse_blockcode(ob *bytes.Buffer, rndr *render, data []byte) int { for end = beg + 1; end < len(data) && data[end-1] != '\n'; end++ { } - chunk := data[beg:end] - if pre := prefix_code(chunk); pre > 0 { + if pre := prefix_code(data[beg:end]); pre > 0 { beg += pre } else { - if is_empty(chunk) == 0 { + if is_empty(data[beg:end]) == 0 { // non-empty non-prefixed line breaks the pre break } @@ -1991,10 +2016,10 @@ func parse_blockcode(ob *bytes.Buffer, rndr *render, data []byte) int { if beg < end { // verbatim copy to the working buffer, escaping entities - if is_empty(chunk) > 0 { + if is_empty(data[beg:end]) > 0 { work.WriteByte('\n') } else { - work.Write(chunk) + work.Write(data[beg:end]) } } beg = end @@ -2181,7 +2206,7 @@ func parse_listitem(ob *bytes.Buffer, rndr *render, data []byte, flags *int) int // intermediate render of inline li if sublist > 0 && sublist < len(workbytes) { parse_inline(inter, rndr, workbytes[:sublist]) - parse_inline(inter, rndr, workbytes[sublist:]) + parse_block(inter, rndr, workbytes[sublist:]) } else { parse_inline(inter, rndr, workbytes) } @@ -2306,7 +2331,31 @@ type html_renderopts struct { } func attr_escape(ob *bytes.Buffer, src []byte) { - ob.WriteString(html.EscapeString(string(src))) + for i := 0; i < len(src); i++ { + // directly copy unescaped characters + org := i + for i < len(src) && src[i] != '<' && src[i] != '>' && src[i] != '&' && src[i] != '"' { + i++ + } + if i > org { + ob.Write(src[org:i]) + } + + // escaping + if i >= len(src) { + break + } + switch src[i] { + case '<': + ob.WriteString("<") + case '>': + ob.WriteString(">") + case '&': + ob.WriteString("&") + case '"': + ob.WriteString(""") + } + } } func unscape_text(ob *bytes.Buffer, src []byte) { @@ -2422,7 +2471,7 @@ func rndr_blockcode(ob *bytes.Buffer, text []byte, lang string, opaque interface func rndr_blockquote(ob *bytes.Buffer, text []byte, opaque interface{}) { ob.WriteString("
\n") ob.Write(text) - ob.WriteString("
\n") + ob.WriteString("") } func rndr_table(ob *bytes.Buffer, header []byte, body []byte, opaque interface{}) { @@ -2729,121 +2778,10 @@ func is_html_tag(tag []byte, tagname string) bool { // // -// Main and public interface +// 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 @@ -2984,3 +2922,79 @@ func Markdown(ob *bytes.Buffer, ib []byte, rndrer *mkd_renderer, extensions uint panic("Nesting level did not end at zero") } } + +func main() { + // configure the rendering engine + 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 + extensions |= MKDEXT_NO_INTRA_EMPHASIS + extensions |= MKDEXT_TABLES + extensions |= MKDEXT_FENCED_CODE + extensions |= MKDEXT_AUTOLINK + extensions |= MKDEXT_STRIKETHROUGH + extensions |= MKDEXT_LAX_HTML_BLOCKS + extensions |= MKDEXT_SPACE_HEADERS + + // read the input + var ib []byte + var err os.Error + switch len(os.Args) { + case 1: + if ib, err = ioutil.ReadAll(os.Stdin); err != nil { + fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err) + os.Exit(-1) + } + case 2, 3: + if ib, err = ioutil.ReadFile(os.Args[1]); err != nil { + fmt.Fprintln(os.Stderr, "Error reading from", os.Args[1], ":", err) + os.Exit(-1) + } + default: + fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[inputfile [outputfile]]") + os.Exit(-1) + } + + // call the main renderer function + ob := bytes.NewBuffer(nil) + Markdown(ob, ib, rndrer, extensions) + + // output the result + if len(os.Args) == 3 { + if err = ioutil.WriteFile(os.Args[2], ob.Bytes(), 0644); err != nil { + fmt.Fprintln(os.Stderr, "Error writing to", os.Args[2], ":", err) + os.Exit(-1) + } + } else { + if _, err = os.Stdout.Write(ob.Bytes()); err != nil { + fmt.Fprintln(os.Stderr, "Error writing to Stdout:", err) + os.Exit(-1) + } + } +}