complete page rendering is now an option in the library

This commit is contained in:
Russ Ross 2011-06-29 10:08:56 -06:00
parent b1a0318250
commit 873a60ad49
6 changed files with 176 additions and 99 deletions

View File

@ -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))
} }

View File

@ -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
View File

@ -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")
} }
} }

View File

@ -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))
} }

View File

@ -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

View File

@ -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))
} }