mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
complete page rendering is now an option in the library
This commit is contained in:
parent
b1a0318250
commit
873a60ad49
|
@ -21,7 +21,7 @@ func runMarkdownBlock(input string, extensions int) string {
|
|||
htmlFlags := 0
|
||||
htmlFlags |= HTML_USE_XHTML
|
||||
|
||||
renderer := HtmlRenderer(htmlFlags)
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
return string(Markdown([]byte(input), renderer, extensions))
|
||||
}
|
||||
|
|
101
example/main.go
101
example/main.go
|
@ -16,15 +16,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"github.com/russross/blackfriday"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DEFAULT_TITLE = ""
|
||||
|
||||
func main() {
|
||||
// parse command-line options
|
||||
var page, xhtml, latex, smartypants, latexdashes, fractions bool
|
||||
|
@ -128,7 +130,12 @@ func main() {
|
|||
if latexdashes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
||||
}
|
||||
renderer = blackfriday.HtmlRenderer(htmlFlags)
|
||||
title := ""
|
||||
if page {
|
||||
htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
|
||||
title = getTitle(input)
|
||||
}
|
||||
renderer = blackfriday.HtmlRenderer(htmlFlags, title, css)
|
||||
}
|
||||
|
||||
// parse and render
|
||||
|
@ -149,49 +156,57 @@ func main() {
|
|||
out = os.Stdout
|
||||
}
|
||||
|
||||
if page {
|
||||
// if it starts with an <h1>, make that the title
|
||||
title := ""
|
||||
if bytes.HasPrefix(output, []byte("<h1>")) {
|
||||
end := 0
|
||||
// we know the buffer ends with a newline, so no need to check bounds
|
||||
for output[end] != '\n' {
|
||||
end++
|
||||
}
|
||||
if bytes.HasSuffix(output[:end], []byte("</h1>")) {
|
||||
title = string(output[len("<h1>") : end-len("</h1>")])
|
||||
}
|
||||
}
|
||||
|
||||
ending := ""
|
||||
if xhtml {
|
||||
fmt.Fprint(out, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||
fmt.Fprintln(out, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
|
||||
fmt.Fprintln(out, "<html xmlns=\"http://www.w3.org/1999/xhtml\">")
|
||||
ending = " /"
|
||||
} else {
|
||||
fmt.Fprint(out, "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" ")
|
||||
fmt.Fprintln(out, "\"http://www.w3.org/TR/html4/strict.dtd\">")
|
||||
fmt.Fprintln(out, "<html>")
|
||||
}
|
||||
fmt.Fprintln(out, "<head>")
|
||||
fmt.Fprintf(out, " <title>%s</title>\n", title)
|
||||
fmt.Fprintf(out, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v%s\"%s>\n",
|
||||
blackfriday.VERSION, ending)
|
||||
fmt.Fprintf(out, " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"%s>\n",
|
||||
ending)
|
||||
if css != "" {
|
||||
fmt.Fprintf(out, " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\" />\n", css)
|
||||
}
|
||||
fmt.Fprintln(out, "</head>")
|
||||
fmt.Fprintln(out, "<body>")
|
||||
}
|
||||
if _, err = out.Write(output); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error writing output:", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
if page {
|
||||
fmt.Fprintln(out, "</body>")
|
||||
fmt.Fprintln(out, "</html>")
|
||||
}
|
||||
}
|
||||
|
||||
// try to guess the title from the input buffer
|
||||
// just check if it starts with an <h1> element and use that
|
||||
func getTitle(input []byte) string {
|
||||
i := 0
|
||||
|
||||
// skip blank lines
|
||||
for i < len(input) && (input[i] == '\n' || input[i] == '\r') {
|
||||
i++
|
||||
}
|
||||
if i >= len(input) {
|
||||
return DEFAULT_TITLE
|
||||
}
|
||||
if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// find the first line
|
||||
start := i
|
||||
for i < len(input) && input[i] != '\n' && input[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
line1 := input[start:i]
|
||||
if input[i] == '\r' && i+1 < len(input) && input[i+1] == '\n' {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
|
||||
// check for a prefix header
|
||||
if len(line1) >= 3 && line1[0] == '#' && (line1[1] == ' ' || line1[1] == '\t') {
|
||||
return strings.TrimSpace(string(line1[2:]))
|
||||
}
|
||||
|
||||
// check for an underlined header
|
||||
if i >= len(input) || input[i] != '=' {
|
||||
return DEFAULT_TITLE
|
||||
}
|
||||
for i < len(input) && input[i] == '=' {
|
||||
i++
|
||||
}
|
||||
for i < len(input) && (input[i] == ' ' || input[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i >= len(input) || (input[i] != '\n' && input[i] != '\r') {
|
||||
return DEFAULT_TITLE
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(line1))
|
||||
}
|
||||
|
|
164
html.go
164
html.go
|
@ -28,6 +28,7 @@ const (
|
|||
HTML_SKIP_LINKS
|
||||
HTML_SAFELINK
|
||||
HTML_TOC
|
||||
HTML_COMPLETE_PAGE
|
||||
HTML_GITHUB_BLOCKCODE
|
||||
HTML_USE_XHTML
|
||||
HTML_USE_SMARTYPANTS
|
||||
|
@ -36,19 +37,23 @@ const (
|
|||
)
|
||||
|
||||
type htmlOptions struct {
|
||||
flags int
|
||||
flags int // HTML_* options
|
||||
closeTag string // how to end singleton tags: either " />\n" or ">\n"
|
||||
tocData struct {
|
||||
headerCount int
|
||||
currentLevel int
|
||||
}
|
||||
title string // document title
|
||||
css string // optional css file url (used with HTML_COMPLETE_PAGE)
|
||||
|
||||
// table of contents data
|
||||
headerCount int
|
||||
currentLevel int
|
||||
toc *bytes.Buffer
|
||||
|
||||
smartypants *SmartypantsRenderer
|
||||
}
|
||||
|
||||
var xhtmlClose = " />\n"
|
||||
var htmlClose = ">\n"
|
||||
|
||||
func HtmlRenderer(flags int) *Renderer {
|
||||
func HtmlRenderer(flags int, title string, css string) *Renderer {
|
||||
// configure the rendering engine
|
||||
r := new(Renderer)
|
||||
r.BlockCode = htmlBlockCode
|
||||
|
@ -73,34 +78,34 @@ func HtmlRenderer(flags int) *Renderer {
|
|||
r.RawHtmlTag = htmlRawTag
|
||||
r.TripleEmphasis = htmlTripleEmphasis
|
||||
r.StrikeThrough = htmlStrikeThrough
|
||||
|
||||
r.Entity = htmlEntity
|
||||
r.NormalText = htmlNormalText
|
||||
|
||||
r.DocumentHeader = htmlDocumentHeader
|
||||
r.DocumentFooter = htmlDocumentFooter
|
||||
|
||||
closeTag := htmlClose
|
||||
if flags&HTML_USE_XHTML != 0 {
|
||||
closeTag = xhtmlClose
|
||||
}
|
||||
r.Opaque = &htmlOptions{flags: flags, closeTag: closeTag, smartypants: Smartypants(flags)}
|
||||
return r
|
||||
}
|
||||
|
||||
func HtmlTocRenderer(flags int) *Renderer {
|
||||
// configure the rendering engine
|
||||
r := new(Renderer)
|
||||
r.Header = htmlTocHeader
|
||||
|
||||
r.CodeSpan = htmlCodeSpan
|
||||
r.DoubleEmphasis = htmlDoubleEmphasis
|
||||
r.Emphasis = htmlEmphasis
|
||||
r.TripleEmphasis = htmlTripleEmphasis
|
||||
r.StrikeThrough = htmlStrikeThrough
|
||||
|
||||
r.DocumentFooter = htmlTocFinalize
|
||||
|
||||
closeTag := ">\n"
|
||||
if flags&HTML_USE_XHTML != 0 {
|
||||
closeTag = " />\n"
|
||||
var toc *bytes.Buffer
|
||||
if flags&HTML_TOC != 0 {
|
||||
toc = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
r.Opaque = &htmlOptions{
|
||||
flags: flags,
|
||||
closeTag: closeTag,
|
||||
title: title,
|
||||
css: css,
|
||||
|
||||
headerCount: 0,
|
||||
currentLevel: 0,
|
||||
toc: toc,
|
||||
|
||||
smartypants: Smartypants(flags),
|
||||
}
|
||||
r.Opaque = &htmlOptions{flags: flags | HTML_TOC, closeTag: closeTag}
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -159,8 +164,8 @@ func htmlHeader(out *bytes.Buffer, text func() bool, level int, opaque interface
|
|||
}
|
||||
|
||||
if options.flags&HTML_TOC != 0 {
|
||||
out.WriteString(fmt.Sprintf("<h%d id=\"toc_%d\">", level, options.tocData.headerCount))
|
||||
options.tocData.headerCount++
|
||||
out.WriteString(fmt.Sprintf("<h%d id=\"toc_%d\">", level, options.headerCount))
|
||||
options.headerCount++
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("<h%d>", level))
|
||||
}
|
||||
|
@ -169,6 +174,12 @@ func htmlHeader(out *bytes.Buffer, text func() bool, level int, opaque interface
|
|||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
|
||||
// are we building a table of contents?
|
||||
if options.flags&HTML_TOC != 0 {
|
||||
htmlTocHeader(out.Bytes()[marker:], level, opaque)
|
||||
}
|
||||
|
||||
out.WriteString(fmt.Sprintf("</h%d>\n", level))
|
||||
}
|
||||
|
||||
|
@ -553,6 +564,10 @@ func htmlStrikeThrough(out *bytes.Buffer, text []byte, opaque interface{}) bool
|
|||
return true
|
||||
}
|
||||
|
||||
func htmlEntity(out *bytes.Buffer, entity []byte, opaque interface{}) {
|
||||
out.Write(entity)
|
||||
}
|
||||
|
||||
func htmlNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
|
||||
options := opaque.(*htmlOptions)
|
||||
if options.flags&HTML_USE_SMARTYPANTS != 0 {
|
||||
|
@ -562,46 +577,93 @@ func htmlNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func htmlTocHeader(out *bytes.Buffer, text func() bool, level int, opaque interface{}) {
|
||||
func htmlTocHeader(text []byte, level int, opaque interface{}) {
|
||||
options := opaque.(*htmlOptions)
|
||||
marker := out.Len()
|
||||
|
||||
for level > options.tocData.currentLevel {
|
||||
if options.tocData.currentLevel > 0 {
|
||||
out.WriteString("<li>")
|
||||
for level > options.currentLevel {
|
||||
if options.currentLevel > 0 {
|
||||
options.toc.WriteString("<li>")
|
||||
}
|
||||
out.WriteString("<ul>\n")
|
||||
options.tocData.currentLevel++
|
||||
options.toc.WriteString("<ul>\n")
|
||||
options.currentLevel++
|
||||
}
|
||||
|
||||
for level < options.tocData.currentLevel {
|
||||
out.WriteString("</ul>")
|
||||
if options.tocData.currentLevel > 1 {
|
||||
out.WriteString("</li>\n")
|
||||
for level < options.currentLevel {
|
||||
options.toc.WriteString("</ul>")
|
||||
if options.currentLevel > 1 {
|
||||
options.toc.WriteString("</li>\n")
|
||||
}
|
||||
options.tocData.currentLevel--
|
||||
options.currentLevel--
|
||||
}
|
||||
|
||||
out.WriteString("<li><a href=\"#toc_")
|
||||
out.WriteString(strconv.Itoa(options.tocData.headerCount))
|
||||
out.WriteString("\">")
|
||||
options.tocData.headerCount++
|
||||
options.toc.WriteString("<li><a href=\"#toc_")
|
||||
options.toc.WriteString(strconv.Itoa(options.headerCount))
|
||||
options.toc.WriteString("\">")
|
||||
options.headerCount++
|
||||
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
options.toc.Write(text)
|
||||
|
||||
options.toc.WriteString("</a></li>\n")
|
||||
}
|
||||
|
||||
func htmlDocumentHeader(out *bytes.Buffer, opaque interface{}) {
|
||||
options := opaque.(*htmlOptions)
|
||||
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||
return
|
||||
}
|
||||
out.WriteString("</a></li>\n")
|
||||
|
||||
ending := ""
|
||||
if options.flags&HTML_USE_XHTML != 0 {
|
||||
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||
out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||
out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||
ending = " /"
|
||||
} else {
|
||||
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" ")
|
||||
out.WriteString("\"http://www.w3.org/TR/html4/strict.dtd\">\n")
|
||||
out.WriteString("<html>\n")
|
||||
}
|
||||
out.WriteString("<head>\n")
|
||||
out.WriteString(" <title>")
|
||||
htmlNormalText(out, []byte(options.title), opaque)
|
||||
out.WriteString("</title>\n")
|
||||
out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
|
||||
out.WriteString(VERSION)
|
||||
out.WriteString("\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
out.WriteString(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
if options.css != "" {
|
||||
out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
|
||||
attrEscape(out, []byte(options.css))
|
||||
out.WriteString("\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
}
|
||||
out.WriteString("</head>\n")
|
||||
out.WriteString("<body>\n")
|
||||
}
|
||||
|
||||
func htmlDocumentFooter(out *bytes.Buffer, opaque interface{}) {
|
||||
options := opaque.(*htmlOptions)
|
||||
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("\n</body>\n")
|
||||
out.WriteString("</html>\n")
|
||||
}
|
||||
|
||||
func htmlTocFinalize(out *bytes.Buffer, opaque interface{}) {
|
||||
options := opaque.(*htmlOptions)
|
||||
for options.tocData.currentLevel > 1 {
|
||||
for options.currentLevel > 1 {
|
||||
out.WriteString("</ul></li>\n")
|
||||
options.tocData.currentLevel--
|
||||
options.currentLevel--
|
||||
}
|
||||
|
||||
if options.tocData.currentLevel > 0 {
|
||||
if options.currentLevel > 0 {
|
||||
out.WriteString("</ul>\n")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func runMarkdownInline(input string) string {
|
|||
htmlFlags := 0
|
||||
htmlFlags |= HTML_USE_XHTML
|
||||
|
||||
renderer := HtmlRenderer(htmlFlags)
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
return string(Markdown([]byte(input), renderer, extensions))
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ type Parser struct {
|
|||
func MarkdownBasic(input []byte) []byte {
|
||||
// set up the HTML renderer
|
||||
htmlFlags := HTML_USE_XHTML
|
||||
renderer := HtmlRenderer(htmlFlags)
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
// set up the parser
|
||||
extensions := 0
|
||||
|
@ -182,7 +182,7 @@ func MarkdownCommon(input []byte) []byte {
|
|||
htmlFlags |= HTML_USE_SMARTYPANTS
|
||||
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
|
||||
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
|
||||
renderer := HtmlRenderer(htmlFlags)
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
// set up the parser
|
||||
extensions := 0
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
)
|
||||
|
||||
func runMarkdownReference(input string) string {
|
||||
renderer := HtmlRenderer(0)
|
||||
renderer := HtmlRenderer(0, "", "")
|
||||
return string(Markdown([]byte(input), renderer, 0))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user