diff --git a/README.md b/README.md index 7b97970..b025f1c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ punctuation substitutions, etc.), and it is safe for all utf-8 (unicode) input. HTML output is currently supported, along with Smartypants -extensions. An experimental LaTeX output engine is also included. +extensions. It started as a translation from C of [Sundown][3]. @@ -69,7 +69,7 @@ html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) ### Custom options If you want to customize the set of options, first get a renderer -(currently either the HTML or LaTeX output engines), then use it to +(currently only the HTML output engine), then use it to call the more general `Markdown` function. For examples, see the implementations of `MarkdownBasic` and `MarkdownCommon` in `markdown.go`. @@ -233,15 +233,8 @@ are a few of note: * [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, but for markdown. -* LaTeX output: renders output as LaTeX. This is currently part of the - main Blackfriday repository, but may be split into its own project - in the future. If you are interested in owning and maintaining the - LaTeX output component, please be in touch. - - It renders some basic documents, but is only experimental at this - point. In particular, it does not do any inline escaping, so input - that happens to look like LaTeX code will be passed through without - modification. +* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex): + renders output as LaTeX. Todo diff --git a/block_test.go b/block_test.go index fa51801..0a944a1 100644 --- a/block_test.go +++ b/block_test.go @@ -1601,7 +1601,9 @@ func TestTOC(t *testing.T) { "#", "", } - doTestsBlock(t, tests, TOC) + doTestsParam(t, tests, TestParams{ + HTMLFlags: UseXHTML | TOC, + }) } func TestOmitContents(t *testing.T) { @@ -1625,9 +1627,13 @@ func TestOmitContents(t *testing.T) { "#\n\nfoo", "", } - doTestsBlock(t, tests, TOC|OmitContents) + doTestsParam(t, tests, TestParams{ + HTMLFlags: UseXHTML | TOC | OmitContents, + }) // Now run again: make sure OmitContents implies TOC - doTestsBlock(t, tests, OmitContents) + doTestsParam(t, tests, TestParams{ + HTMLFlags: UseXHTML | OmitContents, + }) } func TestCompletePage(t *testing.T) { diff --git a/helpers_test.go b/helpers_test.go index 99ac806..ecefe2f 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -44,7 +44,6 @@ func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, s func runMarkdown(input string, params TestParams) string { params.HTMLRendererParameters.Flags = params.HTMLFlags - params.HTMLRendererParameters.Extensions = params.Options.Extensions renderer := NewHTMLRenderer(params.HTMLRendererParameters) return string(Markdown([]byte(input), renderer, params.Options)) } @@ -54,8 +53,7 @@ func doTests(t *testing.T, tests []string) { doTestsParam(t, tests, TestParams{ Options: DefaultOptions, HTMLRendererParameters: HTMLRendererParameters{ - Flags: CommonHTMLFlags, - Extensions: CommonExtensions, + Flags: CommonHTMLFlags, }, }) } diff --git a/html.go b/html.go index 10cca9c..ef5cce5 100644 --- a/html.go +++ b/html.go @@ -30,7 +30,6 @@ type HTMLFlags int const ( HTMLFlagsNone HTMLFlags = 0 SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks - SkipStyle // Skip embedded bar", - "

foo

\n\n

color: #f00 bar

\n", - }, TestParams{ - HTMLFlags: SkipStyle, - }) -} - func TestUseXHTML(t *testing.T) { doTestsParam(t, []string{ "---", diff --git a/latex.go b/latex.go deleted file mode 100644 index 900abf0..0000000 --- a/latex.go +++ /dev/null @@ -1,336 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross . -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// -// LaTeX rendering backend -// -// - -package blackfriday - -import ( - "bytes" - "io" -) - -// Latex is a type that implements the Renderer interface for LaTeX output. -// -// Do not create this directly, instead use the NewLatexRenderer function. -type Latex struct { - w bytes.Buffer -} - -// NewLatexRenderer creates and configures a Latex object, which -// satisfies the Renderer interface. -// -// flags is a set of LATEX_* options ORed together (currently no such options -// are defined). -func NewLatexRenderer(flags int) *Latex { - var writer bytes.Buffer - return &Latex{ - w: writer, - } -} - -// render code chunks using verbatim, or listings if we have a language -func (r *Latex) BlockCode(text []byte, lang string) { - if lang == "" { - r.w.WriteString("\n\\begin{verbatim}\n") - } else { - r.w.WriteString("\n\\begin{lstlisting}[language=") - r.w.WriteString(lang) - r.w.WriteString("]\n") - } - r.w.Write(text) - if lang == "" { - r.w.WriteString("\n\\end{verbatim}\n") - } else { - r.w.WriteString("\n\\end{lstlisting}\n") - } -} - -func (r *Latex) TitleBlock(text []byte) { - -} - -func (r *Latex) BlockQuote(text []byte) { - r.w.WriteString("\n\\begin{quotation}\n") - r.w.Write(text) - r.w.WriteString("\n\\end{quotation}\n") -} - -func (r *Latex) BlockHtml(text []byte) { - // a pretty lame thing to do... - r.w.WriteString("\n\\begin{verbatim}\n") - r.w.Write(text) - r.w.WriteString("\n\\end{verbatim}\n") -} - -func (r *Latex) BeginHeader(level int, id string) { - switch level { - case 1: - r.w.WriteString("\n\\section{") - case 2: - r.w.WriteString("\n\\subsection{") - case 3: - r.w.WriteString("\n\\subsubsection{") - case 4: - r.w.WriteString("\n\\paragraph{") - case 5: - r.w.WriteString("\n\\subparagraph{") - case 6: - r.w.WriteString("\n\\textbf{") - } -} - -func (r *Latex) EndHeader(level int, id string, header []byte) { - r.w.WriteString("}\n") -} - -func (r *Latex) HRule() { - r.w.WriteString("\n\\HRule\n") -} - -func (r *Latex) BeginList(flags ListType) { - if flags&ListTypeOrdered != 0 { - r.w.WriteString("\n\\begin{enumerate}\n") - } else { - r.w.WriteString("\n\\begin{itemize}\n") - } -} - -func (r *Latex) EndList(flags ListType) { - if flags&ListTypeOrdered != 0 { - r.w.WriteString("\n\\end{enumerate}\n") - } else { - r.w.WriteString("\n\\end{itemize}\n") - } -} - -func (r *Latex) ListItem(text []byte, flags ListType) { - r.w.WriteString("\n\\item ") - r.w.Write(text) -} - -func (r *Latex) BeginParagraph() { - r.w.WriteString("\n") -} - -func (r *Latex) EndParagraph() { - r.w.WriteString("\n") -} - -func (r *Latex) Table(header []byte, body []byte, columnData []CellAlignFlags) { - r.w.WriteString("\n\\begin{tabular}{") - for _, elt := range columnData { - switch elt { - case TableAlignmentLeft: - r.w.WriteByte('l') - case TableAlignmentRight: - r.w.WriteByte('r') - default: - r.w.WriteByte('c') - } - } - r.w.WriteString("}\n") - r.w.Write(header) - r.w.WriteString(" \\\\\n\\hline\n") - r.w.Write(body) - r.w.WriteString("\n\\end{tabular}\n") -} - -func (r *Latex) TableRow(text []byte) { - r.w.WriteString(" \\\\\n") - r.w.Write(text) -} - -func (r *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align CellAlignFlags) { - if out.Len() > 0 { - out.WriteString(" & ") - } - out.Write(text) -} - -func (r *Latex) TableCell(out *bytes.Buffer, text []byte, align CellAlignFlags) { - if out.Len() > 0 { - out.WriteString(" & ") - } - out.Write(text) -} - -// TODO: this -func (r *Latex) BeginFootnotes() { -} - -// TODO: this -func (r *Latex) EndFootnotes() { -} - -func (r *Latex) FootnoteItem(name, text []byte, flags ListType) { - -} - -func (r *Latex) AutoLink(link []byte, kind autolinkType) { - r.w.WriteString("\\href{") - if kind == emailAutolink { - r.w.WriteString("mailto:") - } - r.w.Write(link) - r.w.WriteString("}{") - r.w.Write(link) - r.w.WriteString("}") -} - -func (r *Latex) CodeSpan(text []byte) { - r.w.WriteString("\\texttt{") - r.escapeSpecialChars(text) - r.w.WriteString("}") -} - -func (r *Latex) DoubleEmphasis(text []byte) { - r.w.WriteString("\\textbf{") - r.w.Write(text) - r.w.WriteString("}") -} - -func (r *Latex) Emphasis(text []byte) { - r.w.WriteString("\\textit{") - r.w.Write(text) - r.w.WriteString("}") -} - -func (r *Latex) Image(link []byte, title []byte, alt []byte) { - if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) { - // treat it like a link - r.w.WriteString("\\href{") - r.w.Write(link) - r.w.WriteString("}{") - r.w.Write(alt) - r.w.WriteString("}") - } else { - r.w.WriteString("\\includegraphics{") - r.w.Write(link) - r.w.WriteString("}") - } -} - -func (r *Latex) LineBreak() { - r.w.WriteString(" \\\\\n") -} - -func (r *Latex) Link(link []byte, title []byte, content []byte) { - r.w.WriteString("\\href{") - r.w.Write(link) - r.w.WriteString("}{") - r.w.Write(content) - r.w.WriteString("}") -} - -func (r *Latex) RawHtmlTag(tag []byte) { -} - -func (r *Latex) TripleEmphasis(text []byte) { - r.w.WriteString("\\textbf{\\textit{") - r.w.Write(text) - r.w.WriteString("}}") -} - -func (r *Latex) StrikeThrough(text []byte) { - r.w.WriteString("\\sout{") - r.w.Write(text) - r.w.WriteString("}") -} - -// TODO: this -func (r *Latex) FootnoteRef(ref []byte, id int) { -} - -func needsBackslash(c byte) bool { - for _, r := range []byte("_{}%$&\\~#") { - if c == r { - return true - } - } - return false -} - -func (r *Latex) escapeSpecialChars(text []byte) { - for i := 0; i < len(text); i++ { - // directly copy normal characters - org := i - - for i < len(text) && !needsBackslash(text[i]) { - i++ - } - if i > org { - r.w.Write(text[org:i]) - } - - // escape a character - if i >= len(text) { - break - } - r.w.WriteByte('\\') - r.w.WriteByte(text[i]) - } -} - -func (r *Latex) Entity(entity []byte) { - // TODO: convert this into a unicode character or something - r.w.Write(entity) -} - -func (r *Latex) NormalText(text []byte) { - r.escapeSpecialChars(text) -} - -// header and footer -func (r *Latex) DocumentHeader() { - r.w.WriteString("\\documentclass{article}\n") - r.w.WriteString("\n") - r.w.WriteString("\\usepackage{graphicx}\n") - r.w.WriteString("\\usepackage{listings}\n") - r.w.WriteString("\\usepackage[margin=1in]{geometry}\n") - r.w.WriteString("\\usepackage[utf8]{inputenc}\n") - r.w.WriteString("\\usepackage{verbatim}\n") - r.w.WriteString("\\usepackage[normalem]{ulem}\n") - r.w.WriteString("\\usepackage{hyperref}\n") - r.w.WriteString("\n") - r.w.WriteString("\\hypersetup{colorlinks,%\n") - r.w.WriteString(" citecolor=black,%\n") - r.w.WriteString(" filecolor=black,%\n") - r.w.WriteString(" linkcolor=black,%\n") - r.w.WriteString(" urlcolor=black,%\n") - r.w.WriteString(" pdfstartview=FitH,%\n") - r.w.WriteString(" breaklinks=true,%\n") - r.w.WriteString(" pdfauthor={Blackfriday Markdown Processor v") - r.w.WriteString(Version) - r.w.WriteString("}}\n") - r.w.WriteString("\n") - r.w.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n") - r.w.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n") - r.w.WriteString("\\parindent=0pt\n") - r.w.WriteString("\n") - r.w.WriteString("\\begin{document}\n") -} - -func (r *Latex) DocumentFooter() { - r.w.WriteString("\n\\end{document}\n") -} - -func (r *Latex) Render(ast *Node) []byte { - // TODO - return nil -} - -func (r *Latex) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { - // TODO - return GoToNext -} diff --git a/markdown.go b/markdown.go index 1739528..9c3fdb0 100644 --- a/markdown.go +++ b/markdown.go @@ -46,8 +46,6 @@ const ( AutoHeaderIDs // Create the header ID from the text BackslashLineBreak // Translate trailing backslashes into line breaks DefinitionLists // Render definition lists - TOC // Generate a table of contents - OmitContents // Skip the main contents (for a standalone table of contents) CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes @@ -86,7 +84,7 @@ type CellAlignFlags int // Only a single one of these values will be used; they are not ORed together. // These are mostly of interest if you are writing a new output format. const ( - TableAlignmentLeft = 1 << iota + TableAlignmentLeft CellAlignFlags = 1 << iota TableAlignmentRight TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) ) @@ -153,7 +151,8 @@ var blockTags = map[string]struct{}{ // If the callback returns false, the rendering function should reset the // output buffer as though it had never been called. // -// Currently HTML and Latex implementations are provided +// Only an HTML implementation is provided in this repository, +// see the README for external implementations. type Renderer interface { Render(ast *Node) []byte RenderNode(w io.Writer, node *Node, entering bool) WalkStatus @@ -213,14 +212,16 @@ func (p *parser) finalize(block *Node) { } func (p *parser) addChild(node NodeType, offset uint32) *Node { - for !p.tip.canContain(node) { + return p.addExistingChild(NewNode(node), offset) +} + +func (p *parser) addExistingChild(node *Node, offset uint32) *Node { + for !p.tip.canContain(node.Type) { p.finalize(p.tip) } - newNode := NewNode(node) - newNode.content = []byte{} - p.tip.AppendChild(newNode) - p.tip = newNode - return newNode + p.tip.AppendChild(node) + p.tip = node + return node } func (p *parser) closeUnmatchedBlocks() { @@ -287,8 +288,7 @@ type Options struct { func MarkdownBasic(input []byte) []byte { // set up the HTML renderer renderer := NewHTMLRenderer(HTMLRendererParameters{ - Flags: UseXHTML, - Extensions: CommonExtensions, + Flags: UseXHTML, }) // set up the parser @@ -316,8 +316,7 @@ func MarkdownBasic(input []byte) []byte { func MarkdownCommon(input []byte) []byte { // set up the HTML renderer renderer := NewHTMLRenderer(HTMLRendererParameters{ - Flags: CommonHTMLFlags, - Extensions: CommonExtensions, + Flags: CommonHTMLFlags, }) return Markdown(input, renderer, DefaultOptions) } @@ -327,8 +326,7 @@ func MarkdownCommon(input []byte) []byte { // The supplied Renderer is used to format the output, and extensions dictates // which non-standard extensions are enabled. // -// To use the supplied HTML or LaTeX renderers, see NewHTMLRenderer and -// NewLatexRenderer, respectively. +// To use the supplied HTML renderer, see NewHTMLRenderer. func Markdown(input []byte, renderer Renderer, options Options) []byte { if renderer == nil { return nil @@ -419,7 +417,8 @@ func (p *parser) parseRefsToAST() { // the fixed initial set. for i := 0; i < len(p.notes); i++ { ref := p.notes[i] - block := p.addBlock(Item, nil) + p.addExistingChild(ref.footnote, 0) + block := ref.footnote block.ListFlags = flags | ListTypeOrdered block.RefLink = ref.link if ref.hasBlock { @@ -513,6 +512,7 @@ type reference struct { title []byte noteID int // 0 if not a footnote ref hasBlock bool + footnote *Node // a link to the Item node within a list of footnotes text []byte // only gets populated by refOverride feature with Reference.Text } diff --git a/node.go b/node.go index 983ec10..8f10698 100644 --- a/node.go +++ b/node.go @@ -84,6 +84,7 @@ type LinkData struct { Destination []byte // Destination is what goes into a href Title []byte // Title is the tooltip thing that goes in a title attribute NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote + Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil. } // CodeBlockData contains fields relevant to a CodeBlock node type. @@ -277,8 +278,8 @@ type NodeVisitor func(node *Node, entering bool) WalkStatus // Walk is a convenience method that instantiates a walker and starts a // traversal of subtree rooted at n. -func (root *Node) Walk(visitor NodeVisitor) { - w := newNodeWalker(root) +func (n *Node) Walk(visitor NodeVisitor) { + w := newNodeWalker(n) for w.current != nil { status := visitor(w.current, w.entering) switch status { @@ -308,7 +309,7 @@ func newNodeWalker(root *Node) *nodeWalker { } func (nw *nodeWalker) next() { - if !nw.entering && nw.current == nw.root { + if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { nw.current = nil return }