Skip to content

Commit

Permalink
Merge pull request #376 from russross/v2-improve-renderer-368
Browse files Browse the repository at this point in the history
v2: improve Renderer and fix #368
  • Loading branch information
rtfb authored Jul 26, 2017
2 parents 70c446a + 427717f commit f86f06b
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 102 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 0 additions & 30 deletions block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1606,36 +1606,6 @@ func TestTOC(t *testing.T) {
})
}

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*",
Expand Down
103 changes: 46 additions & 57 deletions html.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:._-]*"
Expand Down Expand Up @@ -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</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")
io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
ending = " /"
} else {
w.WriteString("<!DOCTYPE html>\n")
w.WriteString("<html>\n")
io.WriteString(w, "<!DOCTYPE html>\n")
io.WriteString(w, "<html>\n")
}
w.WriteString("<head>\n")
w.WriteString(" <title>")
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("</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")
io.WriteString(w, "</title>\n")
io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
io.WriteString(w, Version)
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
io.WriteString(w, " <meta charset=\"utf-8\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
if r.CSS != "" {
w.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
escapeHTML(w, []byte(r.CSS))
w.WriteString("\"")
w.WriteString(ending)
w.WriteString(">\n")
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
if r.Icon != "" {
w.WriteString(" <link rel=\"icon\" type=\"image/x-icon\" href=\"")
io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
escapeHTML(w, []byte(r.Icon))
w.WriteString("\"")
w.WriteString(ending)
w.WriteString(">\n")
io.WriteString(w, "\"")
io.WriteString(w, ending)
io.WriteString(w, ">\n")
}
w.WriteString("</head>\n")
w.WriteString("<body>\n\n")
io.WriteString(w, "</head>\n")
io.WriteString(w, "<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
Expand Down Expand Up @@ -914,35 +929,9 @@ func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
}

if buf.Len() > 0 {
w.WriteString("<nav>\n")
io.WriteString(w, "<nav>\n")
w.Write(buf.Bytes())
w.WriteString("\n\n</nav>\n")
io.WriteString(w, "\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()
}
46 changes: 32 additions & 14 deletions markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f86f06b

Please sign in to comment.