all repos — grayfriday @ 479920a987fe3ccb2929da9370b4b34cd6053f9a

blackfriday fork with a few changes

Improve the Renderer interface

Improve Renderer to be less confusing. Fix documentation for it.

OmitContents flag got dropped along the way. First, it would fit poorly
into the new design and second, it's unclear how widely this feature is
used. But most importantly, it's trivial to roll your own with the v2
API: https://gist.github.com/rtfb/2693f6bfcc1760661e8d2fb832763a15

Fixes #368.
Vytautas Ĺ altenis vytas@rtfb.lt
Sun, 09 Jul 2017 15:20:34 +0300
commit

479920a987fe3ccb2929da9370b4b34cd6053f9a

parent

70c446a327c4e69c0f661dc092d9729c3af7472c

4 files changed, 85 insertions(+), 108 deletions(-)

jump to
M README.mdREADME.md

@@ -134,7 +134,7 @@ known inputs that make it crash. If you find one, please let me

know and send me the input that does it. NOTE: "safety" in this context means *runtime safety only*. In order to - protect yourself agains JavaScript injection in untrusted content, see + protect yourself against JavaScript injection in untrusted content, see [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content). * **Fast processing**. It is fast enough to render on-demand in
M block_test.goblock_test.go

@@ -1606,36 +1606,6 @@ HTMLFlags: UseXHTML | TOC,

}) } -func TestOmitContents(t *testing.T) { - var tests = []string{ - "# Title\n\n##Subtitle\n\n#Title2", - `<nav> - -<ul> -<li><a href="#toc_0">Title</a> -<ul> -<li><a href="#toc_1">Subtitle</a></li> -</ul></li> - -<li><a href="#toc_2">Title2</a></li> -</ul> - -</nav> -`, - - // Make sure OmitContents omits even with no TOC - "#\n\nfoo", - "", - } - doTestsParam(t, tests, TestParams{ - HTMLFlags: UseXHTML | TOC | OmitContents, - }) - // Now run again: make sure OmitContents implies TOC - doTestsParam(t, tests, TestParams{ - HTMLFlags: UseXHTML | OmitContents, - }) -} - func TestCompletePage(t *testing.T) { var tests = []string{ "*foo*",
M html.gohtml.go

@@ -45,7 +45,6 @@ SmartypantsDashes // Enable smart dashes (with Smartypants)

SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering TOC // Generate a table of contents - OmitContents // Skip the main contents (for a standalone table of contents) TagName = "[A-Za-z][A-Za-z0-9-]*" AttributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"

@@ -819,55 +818,71 @@ }

return GoToNext } -func (r *HTMLRenderer) writeDocumentHeader(w *bytes.Buffer) { +// RenderHeader writes HTML document preamble and TOC if requested. +func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) { + r.writeDocumentHeader(w) + if r.Flags&TOC != 0 { + r.writeTOC(w, ast) + } +} + +// RenderFooter writes HTML document footer. +func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) { + if r.Flags&CompletePage == 0 { + return + } + w.Write([]byte("\n</body>\n</html>\n")) +} + +func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { if r.Flags&CompletePage == 0 { return } ending := "" if r.Flags&UseXHTML != 0 { - w.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") - w.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n") - w.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n") + w.Write([]byte("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")) + w.Write([]byte("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")) + w.Write([]byte("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")) ending = " /" } else { - w.WriteString("<!DOCTYPE html>\n") - w.WriteString("<html>\n") + w.Write([]byte("<!DOCTYPE html>\n")) + w.Write([]byte("<html>\n")) } - w.WriteString("<head>\n") - w.WriteString(" <title>") + w.Write([]byte("<head>\n")) + w.Write([]byte(" <title>")) if r.Flags&Smartypants != 0 { r.sr.Process(w, []byte(r.Title)) } else { escapeHTML(w, []byte(r.Title)) } - w.WriteString("</title>\n") - w.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") - w.WriteString(Version) - w.WriteString("\"") - w.WriteString(ending) - w.WriteString(">\n") - w.WriteString(" <meta charset=\"utf-8\"") - w.WriteString(ending) - w.WriteString(">\n") + w.Write([]byte("</title>\n")) + w.Write([]byte(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")) + w.Write([]byte(Version)) + w.Write([]byte("\"")) + w.Write([]byte(ending)) + w.Write([]byte(">\n")) + w.Write([]byte(" <meta charset=\"utf-8\"")) + w.Write([]byte(ending)) + w.Write([]byte(">\n")) if r.CSS != "" { - w.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"") + w.Write([]byte(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")) escapeHTML(w, []byte(r.CSS)) - w.WriteString("\"") - w.WriteString(ending) - w.WriteString(">\n") + w.Write([]byte("\"")) + w.Write([]byte(ending)) + w.Write([]byte(">\n")) } if r.Icon != "" { - w.WriteString(" <link rel=\"icon\" type=\"image/x-icon\" href=\"") + w.Write([]byte(" <link rel=\"icon\" type=\"image/x-icon\" href=\"")) escapeHTML(w, []byte(r.Icon)) - w.WriteString("\"") - w.WriteString(ending) - w.WriteString(">\n") + w.Write([]byte("\"")) + w.Write([]byte(ending)) + w.Write([]byte(">\n")) } - w.WriteString("</head>\n") - w.WriteString("<body>\n\n") + w.Write([]byte("</head>\n")) + w.Write([]byte("<body>\n\n")) } -func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) { +func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) { buf := bytes.Buffer{} inHeading := false

@@ -880,24 +895,24 @@ inHeading = entering

if entering { node.HeadingID = fmt.Sprintf("toc_%d", headingCount) if node.Level == tocLevel { - buf.WriteString("</li>\n\n<li>") + buf.Write([]byte("</li>\n\n<li>")) } else if node.Level < tocLevel { for node.Level < tocLevel { tocLevel-- - buf.WriteString("</li>\n</ul>") + buf.Write([]byte("</li>\n</ul>")) } - buf.WriteString("</li>\n\n<li>") + buf.Write([]byte("</li>\n\n<li>")) } else { for node.Level > tocLevel { tocLevel++ - buf.WriteString("\n<ul>\n<li>") + buf.Write([]byte("\n<ul>\n<li>")) } } fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount) headingCount++ } else { - buf.WriteString("</a>") + buf.Write([]byte("</a>")) } return GoToNext }

@@ -910,39 +925,13 @@ return GoToNext

}) for ; tocLevel > 0; tocLevel-- { - buf.WriteString("</li>\n</ul>") + buf.Write([]byte("</li>\n</ul>")) } if buf.Len() > 0 { - w.WriteString("<nav>\n") + w.Write([]byte("<nav>\n")) w.Write(buf.Bytes()) - w.WriteString("\n\n</nav>\n") + w.Write([]byte("\n\n</nav>\n")) } r.lastOutputLen = buf.Len() } - -func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) { - if r.Flags&CompletePage == 0 { - return - } - w.WriteString("\n</body>\n</html>\n") -} - -// Render walks the specified syntax (sub)tree and returns a HTML document. -func (r *HTMLRenderer) Render(ast *Node) []byte { - //println("render_Blackfriday") - //dump(ast) - var buf bytes.Buffer - r.writeDocumentHeader(&buf) - if r.Flags&TOC != 0 || r.Flags&OmitContents != 0 { - r.writeTOC(&buf, ast) - if r.Flags&OmitContents != 0 { - return buf.Bytes() - } - } - ast.Walk(func(node *Node, entering bool) WalkStatus { - return r.RenderNode(&buf, node, entering) - }) - r.writeDocumentFooter(&buf) - return buf.Bytes() -}
M markdown.gomarkdown.go

@@ -134,22 +134,33 @@ "section": struct{}{},

"video": struct{}{}, } -// Renderer is the rendering interface. -// This is mostly of interest if you are implementing a new rendering format. -// -// When a byte slice is provided, it contains the (rendered) contents of the -// element. +// Renderer is the rendering interface. This is mostly of interest if you are +// implementing a new rendering format. // -// When a callback is provided instead, it will write the contents of the -// respective element directly to the output buffer and return true on success. -// If the callback returns false, the rendering function should reset the -// output buffer as though it had never been called. -// -// Only an HTML implementation is provided in this repository, -// see the README for external implementations. +// Only an HTML implementation is provided in this repository, see the README +// for external implementations. type Renderer interface { - Render(ast *Node) []byte + // RenderNode is the main rendering method. It will be called once for + // every leaf node and twice for every non-leaf node (first with + // entering=true, then with entering=false). The method should write its + // rendition of the node to the supplied writer w. RenderNode(w io.Writer, node *Node, entering bool) WalkStatus + + // RenderHeader is a method that allows the renderer to produce some + // content preceding the main body of the output document. The header is + // understood in the broad sense here. For example, the default HTML + // renderer will write not only the HTML document preamble, but also the + // table of contents if it was requested. + // + // The method will be passed an entire document tree, in case a particular + // implementation needs to inspect it to produce output. + // + // The output should be written to the supplied writer w. If your + // implementation has no header to write, supply an empty implementation. + RenderHeader(w io.Writer, ast *Node) + + // RenderFooter is a symmetric counterpart of RenderHeader. + RenderFooter(w io.Writer, ast *Node) } // Callback functions for inline parsing. One such function is defined

@@ -374,7 +385,14 @@ })

optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)} optList = append(optList, opts...) parser := New(optList...) - return parser.renderer.Render(parser.Parse(input)) + ast := parser.Parse(input) + var buf bytes.Buffer + parser.renderer.RenderHeader(&buf, ast) + ast.Walk(func(node *Node, entering bool) WalkStatus { + return parser.renderer.RenderNode(&buf, node, entering) + }) + parser.renderer.RenderFooter(&buf, ast) + return buf.Bytes() } // Parse is an entry point to the parsing part of Blackfriday. It takes an