diff --git a/README.md b/README.md index 1abb934..38aa65d 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,14 @@ All features of upskirt are supported, including: * Good performance. I have not done rigorous benchmarking, but informal testing suggests it is around 3.5x slower than upskirt. - This is an ugly, direct translation from the C code, so - the difference is unlikely to be related to differences in - coding style. There is a lot of bounds checking that is - duplicated (by user code for the application and again by code - the compiler generates) and there is some additional memory - management overhead, since I allocate and garbage collect - buffers instead of explicitly managing them as upskirt does. * Minimal dependencies. blackfriday only depends on standard library packages in Go. The source code is pretty self-contained, so it is easy to add to any project. +* Output successfully validates using the W3C validation tool for + HTML 4.01 and XHTML 1.0 Transitional. + Extensions ---------- @@ -83,15 +79,12 @@ LaTeX Output ------------ A rudimentary LaTeX rendering backend is also included. To see an -example of its usage, comment out this link in `main.go`: +example of its usage, see `main.go`: - renderer := blackfriday.HtmlRenderer(html_flags) - -and uncomment this line: - - renderer := blackfriday.LatexRenderer(0) - -It renders some basic documents, but is only experimental at this point. +It renders some basic documents, but is only experimental at this +point. In particular, it does not do any inline escaping, so input +that happens to look like LaTeX code will be passed through without +modification. Todo diff --git a/example/main.go b/example/main.go index 08287be..4482fd8 100644 --- a/example/main.go +++ b/example/main.go @@ -13,6 +13,7 @@ package main import ( + "flag" "fmt" "io/ioutil" "github.com/russross/blackfriday" @@ -20,22 +21,55 @@ import ( ) func main() { + // parse command-line options + var page, xhtml, latex, smartypants bool + var css string + var repeat int + flag.BoolVar(&page, "page", false, + "Generate a standalone HTML page (implies -latex=false)") + flag.BoolVar(&xhtml, "xhtml", true, + "Use XHTML-style tags in HTML output") + flag.BoolVar(&latex, "latex", false, + "Generate LaTeX output instead of HTML") + flag.BoolVar(&smartypants, "smartypants", false, + "Apply smartypants-style substitutions") + flag.StringVar(&css, "css", "", + "Link to a CSS stylesheet (implies -page)") + flag.IntVar(&repeat, "repeat", 1, + "Process the input multiple times (for benchmarking)") + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage:\n"+ + " %s [options] [inputfile [outputfile]]\n\n"+ + "Options:\n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() + + // enforce implied options + if css != "" { + page = true + } + if page { + latex = false + } + // read the input var input []byte var err os.Error - switch len(os.Args) { - case 1: + args := flag.Args() + switch len(args) { + case 0: if input, err = ioutil.ReadAll(os.Stdin); err != nil { fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err) os.Exit(-1) } - case 2, 3: - if input, err = ioutil.ReadFile(os.Args[1]); err != nil { - fmt.Fprintln(os.Stderr, "Error reading from", os.Args[1], ":", err) + case 1, 2: + if input, err = ioutil.ReadFile(args[0]); err != nil { + fmt.Fprintln(os.Stderr, "Error reading from", args[0], ":", err) os.Exit(-1) } default: - fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[inputfile [outputfile]]") + flag.Usage() os.Exit(-1) } @@ -48,33 +82,70 @@ func main() { extensions |= blackfriday.EXTENSION_STRIKETHROUGH extensions |= blackfriday.EXTENSION_SPACE_HEADERS - html_flags := 0 - html_flags |= blackfriday.HTML_USE_XHTML - // note: uncomment the following line to enable smartypants - // it is commented out by default so that markdown - // compatibility tests pass - //html_flags |= blackfriday.HTML_USE_SMARTYPANTS - html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS - html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES + var renderer *blackfriday.Renderer + if latex { + // render the data into LaTeX + renderer = blackfriday.LatexRenderer(0) + } else { + // render the data into HTML + html_flags := 0 + if xhtml { + html_flags |= blackfriday.HTML_USE_XHTML + } + if smartypants { + html_flags |= blackfriday.HTML_USE_SMARTYPANTS + html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS + html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES + } + renderer = blackfriday.HtmlRenderer(html_flags) + } - // render the data into HTML (comment this out to deselect HTML) - renderer := blackfriday.HtmlRenderer(html_flags) - - // render the data into LaTeX (uncomment to select LaTeX) - //renderer := blackfriday.LatexRenderer(0) - - output := blackfriday.Markdown(input, renderer, extensions) + // parse and render + var output []byte + for i := 0; i < repeat; i++ { + output = blackfriday.Markdown(input, renderer, extensions) + } // output the result - if len(os.Args) == 3 { - if err = ioutil.WriteFile(os.Args[2], output, 0644); err != nil { - fmt.Fprintln(os.Stderr, "Error writing to", os.Args[2], ":", err) + var out *os.File + if len(args) == 2 { + if out, err = os.Create(args[1]); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", args[1], err) os.Exit(-1) } + defer out.Close() } else { - if _, err = os.Stdout.Write(output); err != nil { - fmt.Fprintln(os.Stderr, "Error writing to Stdout:", err) - os.Exit(-1) + out = os.Stdout + } + + if page { + ending := "" + if xhtml { + fmt.Fprint(out, "") + fmt.Fprintln(out, "") + ending = " /" + } else { + fmt.Fprint(out, "") + fmt.Fprintln(out, "") } + fmt.Fprintln(out, "") + fmt.Fprintln(out, " ") + fmt.Fprintf(out, " \n", ending) + fmt.Fprintf(out, " \n", ending) + if css != "" { + fmt.Fprintf(out, " \n", css) + } + fmt.Fprintln(out, "") + fmt.Fprintln(out, "") + } + if _, err = out.Write(output); err != nil { + fmt.Fprintln(os.Stderr, "Error writing output:", err) + os.Exit(-1) + } + if page { + fmt.Fprintln(out, "") + fmt.Fprintln(out, "") } } diff --git a/html.go b/html.go index a1fcd46..0dead5e 100644 --- a/html.go +++ b/html.go @@ -419,7 +419,7 @@ func htmlAutoLink(out *bytes.Buffer, link []byte, kind int, opaque interface{}) if kind == LINK_TYPE_EMAIL { out.WriteString("mailto:") } - out.Write(link) + attrEscape(out, link) out.WriteString("\">") /* @@ -504,17 +504,13 @@ func htmlLink(out *bytes.Buffer, link []byte, title []byte, content []byte, opaq } out.WriteString(" 0 { - out.Write(link) - } + attrEscape(out, link) if len(title) > 0 { out.WriteString("\" title=\"") attrEscape(out, title) } out.WriteString("\">") - if len(content) > 0 { - out.Write(content) - } + out.Write(content) out.WriteString("") return 1 } diff --git a/inline.go b/inline.go index 987e862..24f793c 100644 --- a/inline.go +++ b/inline.go @@ -124,7 +124,7 @@ func inlineCodeSpan(out *bytes.Buffer, rndr *render, data []byte, offset int) in } if i < nb && end >= len(data) { - out.WriteByte('`') + out.WriteByte('`') return 0 // no matching delimiter } @@ -185,6 +185,11 @@ func inlineLineBreak(out *bytes.Buffer, rndr *render, data []byte, offset int) i // '[': parse a link or an image func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { + // no links allowed inside other links + if rndr.insideLink { + return 0 + } + isImg := offset > 0 && data[offset-1] == '!' data = data[offset:] @@ -410,7 +415,11 @@ func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { if isImg { content.Write(data[1:txt_e]) } else { + // links cannot contain other links, so turn off link parsing temporarily + insideLink := rndr.insideLink + rndr.insideLink = true parseInline(&content, rndr, data[1:txt_e]) + rndr.insideLink = insideLink } } @@ -539,7 +548,7 @@ func inlineEntity(out *bytes.Buffer, rndr *render, data []byte, offset int) int func inlineAutoLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { // quick check to rule out most false hits on ':' - if len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' { + if rndr.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' { return 0 } diff --git a/markdown.go b/markdown.go index 3127801..4fa2586 100644 --- a/markdown.go +++ b/markdown.go @@ -141,6 +141,7 @@ type render struct { flags uint32 nesting int maxNesting int + insideLink bool } @@ -165,6 +166,7 @@ func Markdown(input []byte, renderer *Renderer, extensions uint32) []byte { rndr.flags = extensions rndr.refs = make(map[string]*reference) rndr.maxNesting = 16 + rndr.insideLink = false // register inline parsers if rndr.mk.Emphasis != nil || rndr.mk.DoubleEmphasis != nil || rndr.mk.TripleEmphasis != nil { @@ -464,7 +466,7 @@ func expandTabs(out *bytes.Buffer, line []byte) { // the slow case: we need to count runes to figure out how // many spaces to insert for each tab column := 0 - i = 0 + i = 0 for i < len(line) { start := i for i < len(line) && line[i] != '\t' { diff --git a/upskirtref/Amps and angle encoding.html b/upskirtref/Amps and angle encoding.html index 138f4d5..483f8ff 100644 --- a/upskirtref/Amps and angle encoding.html +++ b/upskirtref/Amps and angle encoding.html @@ -8,10 +8,10 @@

6 > 5.

-

Here's a link with an ampersand in the URL.

+

Here's a link with an ampersand in the URL.

Here's a link with an amersand in the link text: AT&T.

-

Here's an inline link.

+

Here's an inline link.

-

Here's an inline link.

+

Here's an inline link.

diff --git a/upskirtref/Auto links.html b/upskirtref/Auto links.html index c50507f..16a24d2 100644 --- a/upskirtref/Auto links.html +++ b/upskirtref/Auto links.html @@ -1,6 +1,6 @@

Link: http://example.com/.

-

With an ampersand: http://example.com/?foo=1&bar=2

+

With an ampersand: http://example.com/?foo=1&bar=2