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 := 0
|
||||||
htmlFlags |= HTML_USE_XHTML
|
htmlFlags |= HTML_USE_XHTML
|
||||||
|
|
||||||
renderer := HtmlRenderer(htmlFlags)
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
return string(Markdown([]byte(input), renderer, extensions))
|
return string(Markdown([]byte(input), renderer, extensions))
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
"os"
|
"os"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DEFAULT_TITLE = ""
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// parse command-line options
|
// parse command-line options
|
||||||
var page, xhtml, latex, smartypants, latexdashes, fractions bool
|
var page, xhtml, latex, smartypants, latexdashes, fractions bool
|
||||||
|
@ -128,7 +130,12 @@ func main() {
|
||||||
if latexdashes {
|
if latexdashes {
|
||||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
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
|
// parse and render
|
||||||
|
@ -149,49 +156,57 @@ func main() {
|
||||||
out = os.Stdout
|
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 {
|
if _, err = out.Write(output); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error writing output:", err)
|
fmt.Fprintln(os.Stderr, "Error writing output:", err)
|
||||||
os.Exit(-1)
|
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))
|
||||||
}
|
}
|
||||||
|
|
152
html.go
152
html.go
|
@ -28,6 +28,7 @@ const (
|
||||||
HTML_SKIP_LINKS
|
HTML_SKIP_LINKS
|
||||||
HTML_SAFELINK
|
HTML_SAFELINK
|
||||||
HTML_TOC
|
HTML_TOC
|
||||||
|
HTML_COMPLETE_PAGE
|
||||||
HTML_GITHUB_BLOCKCODE
|
HTML_GITHUB_BLOCKCODE
|
||||||
HTML_USE_XHTML
|
HTML_USE_XHTML
|
||||||
HTML_USE_SMARTYPANTS
|
HTML_USE_SMARTYPANTS
|
||||||
|
@ -36,19 +37,23 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type htmlOptions struct {
|
type htmlOptions struct {
|
||||||
flags int
|
flags int // HTML_* options
|
||||||
closeTag string // how to end singleton tags: either " />\n" or ">\n"
|
closeTag string // how to end singleton tags: either " />\n" or ">\n"
|
||||||
tocData struct {
|
title string // document title
|
||||||
|
css string // optional css file url (used with HTML_COMPLETE_PAGE)
|
||||||
|
|
||||||
|
// table of contents data
|
||||||
headerCount int
|
headerCount int
|
||||||
currentLevel int
|
currentLevel int
|
||||||
}
|
toc *bytes.Buffer
|
||||||
|
|
||||||
smartypants *SmartypantsRenderer
|
smartypants *SmartypantsRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhtmlClose = " />\n"
|
var xhtmlClose = " />\n"
|
||||||
var htmlClose = ">\n"
|
var htmlClose = ">\n"
|
||||||
|
|
||||||
func HtmlRenderer(flags int) *Renderer {
|
func HtmlRenderer(flags int, title string, css string) *Renderer {
|
||||||
// configure the rendering engine
|
// configure the rendering engine
|
||||||
r := new(Renderer)
|
r := new(Renderer)
|
||||||
r.BlockCode = htmlBlockCode
|
r.BlockCode = htmlBlockCode
|
||||||
|
@ -73,34 +78,34 @@ func HtmlRenderer(flags int) *Renderer {
|
||||||
r.RawHtmlTag = htmlRawTag
|
r.RawHtmlTag = htmlRawTag
|
||||||
r.TripleEmphasis = htmlTripleEmphasis
|
r.TripleEmphasis = htmlTripleEmphasis
|
||||||
r.StrikeThrough = htmlStrikeThrough
|
r.StrikeThrough = htmlStrikeThrough
|
||||||
|
|
||||||
|
r.Entity = htmlEntity
|
||||||
r.NormalText = htmlNormalText
|
r.NormalText = htmlNormalText
|
||||||
|
|
||||||
|
r.DocumentHeader = htmlDocumentHeader
|
||||||
|
r.DocumentFooter = htmlDocumentFooter
|
||||||
|
|
||||||
closeTag := htmlClose
|
closeTag := htmlClose
|
||||||
if flags&HTML_USE_XHTML != 0 {
|
if flags&HTML_USE_XHTML != 0 {
|
||||||
closeTag = xhtmlClose
|
closeTag = xhtmlClose
|
||||||
}
|
}
|
||||||
r.Opaque = &htmlOptions{flags: flags, closeTag: closeTag, smartypants: Smartypants(flags)}
|
var toc *bytes.Buffer
|
||||||
return r
|
if flags&HTML_TOC != 0 {
|
||||||
|
toc = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HtmlTocRenderer(flags int) *Renderer {
|
r.Opaque = &htmlOptions{
|
||||||
// configure the rendering engine
|
flags: flags,
|
||||||
r := new(Renderer)
|
closeTag: closeTag,
|
||||||
r.Header = htmlTocHeader
|
title: title,
|
||||||
|
css: css,
|
||||||
|
|
||||||
r.CodeSpan = htmlCodeSpan
|
headerCount: 0,
|
||||||
r.DoubleEmphasis = htmlDoubleEmphasis
|
currentLevel: 0,
|
||||||
r.Emphasis = htmlEmphasis
|
toc: toc,
|
||||||
r.TripleEmphasis = htmlTripleEmphasis
|
|
||||||
r.StrikeThrough = htmlStrikeThrough
|
|
||||||
|
|
||||||
r.DocumentFooter = htmlTocFinalize
|
smartypants: Smartypants(flags),
|
||||||
|
|
||||||
closeTag := ">\n"
|
|
||||||
if flags&HTML_USE_XHTML != 0 {
|
|
||||||
closeTag = " />\n"
|
|
||||||
}
|
}
|
||||||
r.Opaque = &htmlOptions{flags: flags | HTML_TOC, closeTag: closeTag}
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,8 +164,8 @@ func htmlHeader(out *bytes.Buffer, text func() bool, level int, opaque interface
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.flags&HTML_TOC != 0 {
|
if options.flags&HTML_TOC != 0 {
|
||||||
out.WriteString(fmt.Sprintf("<h%d id=\"toc_%d\">", level, options.tocData.headerCount))
|
out.WriteString(fmt.Sprintf("<h%d id=\"toc_%d\">", level, options.headerCount))
|
||||||
options.tocData.headerCount++
|
options.headerCount++
|
||||||
} else {
|
} else {
|
||||||
out.WriteString(fmt.Sprintf("<h%d>", level))
|
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)
|
out.Truncate(marker)
|
||||||
return
|
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))
|
out.WriteString(fmt.Sprintf("</h%d>\n", level))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,6 +564,10 @@ func htmlStrikeThrough(out *bytes.Buffer, text []byte, opaque interface{}) bool
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func htmlEntity(out *bytes.Buffer, entity []byte, opaque interface{}) {
|
||||||
|
out.Write(entity)
|
||||||
|
}
|
||||||
|
|
||||||
func htmlNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
|
func htmlNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
|
||||||
options := opaque.(*htmlOptions)
|
options := opaque.(*htmlOptions)
|
||||||
if options.flags&HTML_USE_SMARTYPANTS != 0 {
|
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)
|
options := opaque.(*htmlOptions)
|
||||||
marker := out.Len()
|
|
||||||
|
|
||||||
for level > options.tocData.currentLevel {
|
for level > options.currentLevel {
|
||||||
if options.tocData.currentLevel > 0 {
|
if options.currentLevel > 0 {
|
||||||
out.WriteString("<li>")
|
options.toc.WriteString("<li>")
|
||||||
}
|
}
|
||||||
out.WriteString("<ul>\n")
|
options.toc.WriteString("<ul>\n")
|
||||||
options.tocData.currentLevel++
|
options.currentLevel++
|
||||||
}
|
}
|
||||||
|
|
||||||
for level < options.tocData.currentLevel {
|
for level < options.currentLevel {
|
||||||
out.WriteString("</ul>")
|
options.toc.WriteString("</ul>")
|
||||||
if options.tocData.currentLevel > 1 {
|
if options.currentLevel > 1 {
|
||||||
out.WriteString("</li>\n")
|
options.toc.WriteString("</li>\n")
|
||||||
}
|
}
|
||||||
options.tocData.currentLevel--
|
options.currentLevel--
|
||||||
}
|
}
|
||||||
|
|
||||||
out.WriteString("<li><a href=\"#toc_")
|
options.toc.WriteString("<li><a href=\"#toc_")
|
||||||
out.WriteString(strconv.Itoa(options.tocData.headerCount))
|
options.toc.WriteString(strconv.Itoa(options.headerCount))
|
||||||
out.WriteString("\">")
|
options.toc.WriteString("\">")
|
||||||
options.tocData.headerCount++
|
options.headerCount++
|
||||||
|
|
||||||
if !text() {
|
options.toc.Write(text)
|
||||||
out.Truncate(marker)
|
|
||||||
|
options.toc.WriteString("</a></li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlDocumentHeader(out *bytes.Buffer, opaque interface{}) {
|
||||||
|
options := opaque.(*htmlOptions)
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||||
return
|
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{}) {
|
func htmlTocFinalize(out *bytes.Buffer, opaque interface{}) {
|
||||||
options := opaque.(*htmlOptions)
|
options := opaque.(*htmlOptions)
|
||||||
for options.tocData.currentLevel > 1 {
|
for options.currentLevel > 1 {
|
||||||
out.WriteString("</ul></li>\n")
|
out.WriteString("</ul></li>\n")
|
||||||
options.tocData.currentLevel--
|
options.currentLevel--
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.tocData.currentLevel > 0 {
|
if options.currentLevel > 0 {
|
||||||
out.WriteString("</ul>\n")
|
out.WriteString("</ul>\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func runMarkdownInline(input string) string {
|
||||||
htmlFlags := 0
|
htmlFlags := 0
|
||||||
htmlFlags |= HTML_USE_XHTML
|
htmlFlags |= HTML_USE_XHTML
|
||||||
|
|
||||||
renderer := HtmlRenderer(htmlFlags)
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
return string(Markdown([]byte(input), renderer, extensions))
|
return string(Markdown([]byte(input), renderer, extensions))
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ type Parser struct {
|
||||||
func MarkdownBasic(input []byte) []byte {
|
func MarkdownBasic(input []byte) []byte {
|
||||||
// set up the HTML renderer
|
// set up the HTML renderer
|
||||||
htmlFlags := HTML_USE_XHTML
|
htmlFlags := HTML_USE_XHTML
|
||||||
renderer := HtmlRenderer(htmlFlags)
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
// set up the parser
|
// set up the parser
|
||||||
extensions := 0
|
extensions := 0
|
||||||
|
@ -182,7 +182,7 @@ func MarkdownCommon(input []byte) []byte {
|
||||||
htmlFlags |= HTML_USE_SMARTYPANTS
|
htmlFlags |= HTML_USE_SMARTYPANTS
|
||||||
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
|
htmlFlags |= HTML_SMARTYPANTS_FRACTIONS
|
||||||
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
|
htmlFlags |= HTML_SMARTYPANTS_LATEX_DASHES
|
||||||
renderer := HtmlRenderer(htmlFlags)
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
// set up the parser
|
// set up the parser
|
||||||
extensions := 0
|
extensions := 0
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func runMarkdownReference(input string) string {
|
func runMarkdownReference(input string) string {
|
||||||
renderer := HtmlRenderer(0)
|
renderer := HtmlRenderer(0, "", "")
|
||||||
return string(Markdown([]byte(input), renderer, 0))
|
return string(Markdown([]byte(input), renderer, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user