mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
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.
This commit is contained in:
parent
70c446a327
commit
479920a987
|
@ -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
|
||||
|
|
|
@ -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*",
|
||||
|
|
115
html.go
115
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
|
||||
}
|
||||
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 @@ func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
|
|||
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 @@ func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
|
|||
})
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
46
markdown.go
46
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user