diff --git a/README.md b/README.md index 400572f..9ce7334 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ All features of Sundown are supported, including: 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 diff --git a/block_test.go b/block_test.go index eaad6d4..0a2a4d8 100644 --- a/block_test.go +++ b/block_test.go @@ -1606,36 +1606,6 @@ func TestTOC(t *testing.T) { }) } -func TestOmitContents(t *testing.T) { - var tests = []string{ - "# Title\n\n##Subtitle\n\n#Title2", - ` -`, - - // 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*", diff --git a/html.go b/html.go index a47b740..f1a60de 100644 --- a/html.go +++ b/html.go @@ -45,7 +45,6 @@ const ( 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 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt 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 + } + io.WriteString(w, "\n\n\n") +} + +func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { if r.Flags&CompletePage == 0 { return } ending := "" if r.Flags&UseXHTML != 0 { - w.WriteString("\n") - w.WriteString("\n") + io.WriteString(w, "\n") + io.WriteString(w, "\n") ending = " /" } else { - w.WriteString("\n") - w.WriteString("\n") + io.WriteString(w, "\n") + io.WriteString(w, "\n") } - w.WriteString("\n") - w.WriteString(" ") + io.WriteString(w, "<head>\n") + io.WriteString(w, " <title>") if r.Flags&Smartypants != 0 { r.sr.Process(w, []byte(r.Title)) } else { escapeHTML(w, []byte(r.Title)) } - w.WriteString("\n") - w.WriteString(" \n") - w.WriteString(" \n") + io.WriteString(w, "\n") + io.WriteString(w, " \n") + io.WriteString(w, " \n") if r.CSS != "" { - w.WriteString(" \n") + io.WriteString(w, "\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") } if r.Icon != "" { - w.WriteString(" \n") + io.WriteString(w, "\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") } - w.WriteString("\n") - w.WriteString("\n\n") + io.WriteString(w, "\n") + io.WriteString(w, "\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 @@ -914,35 +929,9 @@ func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) { } if buf.Len() > 0 { - w.WriteString("\n") } r.lastOutputLen = buf.Len() } - -func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) { - if r.Flags&CompletePage == 0 { - return - } - w.WriteString("\n\n\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() -} diff --git a/markdown.go b/markdown.go index 9c70e22..d3756b5 100644 --- a/markdown.go +++ b/markdown.go @@ -134,22 +134,33 @@ var blockTags = map[string]struct{}{ "video": struct{}{}, } -// Renderer is the rendering interface. -// This is mostly of interest if you are implementing a new rendering format. +// 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. -// -// 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 @@ func Run(input []byte, opts ...Option) []byte { 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