1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 05:09:42 +02:00

Upgrade to latest Markdown processing engine

This commit is contained in:
Harvey Kandola 2018-02-09 13:34:53 +00:00
parent 1e2c468dc4
commit 0262763c95
22 changed files with 3560 additions and 2394 deletions

View file

@ -24,5 +24,5 @@ import (
// PagesHTML set to the given (*api.DocumentConversionRequest).Filedata converted by the blackfriday lib. // PagesHTML set to the given (*api.DocumentConversionRequest).Filedata converted by the blackfriday lib.
func Convert(ctx context.Context, in interface{}) (interface{}, error) { func Convert(ctx context.Context, in interface{}) (interface{}, error) {
return &api.DocumentConversionResponse{ return &api.DocumentConversionResponse{
PagesHTML: blackfriday.MarkdownCommon(in.(*api.DocumentConversionRequest).Filedata)}, nil PagesHTML: blackfriday.Run(in.(*api.DocumentConversionRequest).Filedata)}, nil
} }

View file

@ -47,7 +47,7 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R
// Render converts markdown data into HTML suitable for browser rendering. // Render converts markdown data into HTML suitable for browser rendering.
func (*Provider) Render(ctx *provider.Context, config, data string) string { func (*Provider) Render(ctx *provider.Context, config, data string) string {
result := blackfriday.MarkdownCommon([]byte(data)) result := blackfriday.Run([]byte(data))
return string(result) return string(result)
} }

View file

@ -8,7 +8,7 @@ punctuation substitutions, etc.), and it is safe for all utf-8
(unicode) input. (unicode) input.
HTML output is currently supported, along with Smartypants 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]. It started as a translation from C of [Sundown][3].
@ -16,63 +16,87 @@ It started as a translation from C of [Sundown][3].
Installation Installation
------------ ------------
Blackfriday is compatible with Go 1. If you are using an older Blackfriday is compatible with any modern Go release. With Go 1.7 and git
release of Go, consider using v1.1 of blackfriday, which was based installed:
on the last stable release of Go prior to Go 1. You can find it as a
tagged commit on github.
With Go 1 and git installed: go get gopkg.in/russross/blackfriday.v2
go get github.com/russross/blackfriday
will download, compile, and install the package into your `$GOPATH` will download, compile, and install the package into your `$GOPATH`
directory hierarchy. Alternatively, you can achieve the same if you directory hierarchy. Alternatively, you can achieve the same if you
import it into a project: import it into a project:
import "github.com/russross/blackfriday" import "gopkg.in/russross/blackfriday.v2"
and `go get` without parameters. and `go get` without parameters.
Versions
--------
Currently maintained and recommended version of Blackfriday is `v2`. It's being
developed on its own branch: https://github.com/russross/blackfriday/v2. You
should install and import it via [gopkg.in][6] at
`gopkg.in/russross/blackfriday.v2`.
Version 2 offers a number of improvements over v1:
* Cleaned up API
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
the document
* Latest bug fixes
* Flexibility to easily add your own rendering extensions
Potential drawbacks:
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
ballpark of around 15%.
* API breakage. If you can't afford modifying your code to adhere to the new API
and don't care too much about the new features, v2 is probably not for you.
* Several bug fixes are trailing behind and still need to be forward-ported to
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
tracking.
Usage Usage
----- -----
For basic usage, it is as simple as getting your input into a byte For the most sensible markdown processing, it is as simple as getting your input
slice and calling: into a byte slice and calling:
output := blackfriday.MarkdownBasic(input) ```go
output := blackfriday.Run(input)
```
This renders it with no extensions enabled. To get a more useful Your input will be parsed and the output rendered with a set of most popular
feature set, use this instead: extensions enabled. If you want the most basic feature set, corresponding with
the bare Markdown specification, use:
output := blackfriday.MarkdownCommon(input) ```go
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
```
### Sanitize untrusted content ### Sanitize untrusted content
Blackfriday itself does nothing to protect against malicious content. If you are Blackfriday itself does nothing to protect against malicious content. If you are
dealing with user-supplied markdown, we recommend running blackfriday's output dealing with user-supplied markdown, we recommend running Blackfriday's output
through HTML sanitizer such as through HTML sanitizer such as [Bluemonday][5].
[Bluemonday](https://github.com/microcosm-cc/bluemonday).
Here's an example of simple usage of blackfriday together with bluemonday: Here's an example of simple usage of Blackfriday together with Bluemonday:
``` go ```go
import ( import (
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
) )
// ... // ...
unsafe := blackfriday.MarkdownCommon(input) unsafe := blackfriday.Run(input)
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
``` ```
### Custom options ### Custom options
If you want to customize the set of options, first get a renderer If you want to customize the set of options, use `blackfriday.WithExtensions`,
(currently either the HTML or LaTeX output engines), then use it to `blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
call the more general `Markdown` function. For examples, see the
implementations of `MarkdownBasic` and `MarkdownCommon` in
`markdown.go`.
You can also check out `blackfriday-tool` for a more complete example You can also check out `blackfriday-tool` for a more complete example
of how to use it. Download and install it using: of how to use it. Download and install it using:
@ -114,7 +138,7 @@ All features of Sundown are supported, including:
know and send me the input that does it. know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to 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). [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in * **Fast processing**. It is fast enough to render on-demand in
@ -160,7 +184,7 @@ implements the following extensions:
and supply a language (to make syntax highlighting simple). Just and supply a language (to make syntax highlighting simple). Just
mark it like this: mark it like this:
``` go ```go
func getTrue() bool { func getTrue() bool {
return true return true
} }
@ -169,16 +193,33 @@ implements the following extensions:
You can use 3 or more backticks to mark the beginning of the You can use 3 or more backticks to mark the beginning of the
block, and the same number to mark the end of the block. block, and the same number to mark the end of the block.
* **Definition lists**. A simple definition list is made of a single-line
term followed by a colon and the definition for that term.
Cat
: Fluffy animal everyone likes
Internet
: Vector of transmission for pictures of cats
Terms must be separated from the previous definition by a blank line.
* **Footnotes**. A marker in the text that will become a superscript number;
a footnote definition that will be placed in a list of footnotes at the
end of the document. A footnote looks like this:
This is a footnote.[^1]
[^1]: the footnote text.
* **Autolinking**. Blackfriday can find URLs that have not been * **Autolinking**. Blackfriday can find URLs that have not been
explicitly marked as links and turn them into links. explicitly marked as links and turn them into links.
* **Strikethrough**. Use two tildes (`~~`) to mark text that * **Strikethrough**. Use two tildes (`~~`) to mark text that
should be crossed out. should be crossed out.
* **Hard line breaks**. With this extension enabled (it is off by * **Hard line breaks**. With this extension enabled newlines in the input
default in the `MarkdownBasic` and `MarkdownCommon` convenience translate into line breaks in the output. This extension is off by default.
functions), newlines in the input translate into line breaks in
the output.
* **Smart quotes**. Smartypants-style punctuation substitution is * **Smart quotes**. Smartypants-style punctuation substitution is
supported, turning normal double- and single-quote marks into supported, turning normal double- and single-quote marks into
@ -205,7 +246,7 @@ are a few of note:
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown): * [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
provides a GitHub Flavored Markdown renderer with fenced code block provides a GitHub Flavored Markdown renderer with fenced code block
highlighting, clickable header anchor links. highlighting, clickable heading anchor links.
It's not customizable, and its goal is to produce HTML output It's not customizable, and its goal is to produce HTML output
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode), equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
@ -214,15 +255,8 @@ are a few of note:
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, * [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown. but for markdown.
* LaTeX output: renders output as LaTeX. This is currently part of the * [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
main Blackfriday repository, but may be split into its own project renders output as LaTeX.
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.
Todo Todo
@ -241,6 +275,9 @@ License
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt) [Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
[1]: http://daringfireball.net/projects/markdown/ "Markdown" [1]: https://daringfireball.net/projects/markdown/ "Markdown"
[2]: http://golang.org/ "Go Language" [2]: https://golang.org/ "Go Language"
[3]: https://github.com/vmg/sundown "Sundown" [3]: https://github.com/vmg/sundown "Sundown"
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
[6]: https://labix.org/gopkg.in "gopkg.in"

File diff suppressed because it is too large Load diff

View file

@ -14,68 +14,10 @@
package blackfriday package blackfriday
import ( import (
"strings"
"testing" "testing"
) )
func runMarkdownBlockWithRenderer(input string, extensions int, renderer Renderer) string {
return string(Markdown([]byte(input), renderer, extensions))
}
func runMarkdownBlock(input string, extensions int) string {
htmlFlags := 0
htmlFlags |= HTML_USE_XHTML
renderer := HtmlRenderer(htmlFlags, "", "")
return runMarkdownBlockWithRenderer(input, extensions, renderer)
}
func runnerWithRendererParameters(parameters HtmlRendererParameters) func(string, int) string {
return func(input string, extensions int) string {
htmlFlags := 0
htmlFlags |= HTML_USE_XHTML
renderer := HtmlRendererWithParameters(htmlFlags, "", "", parameters)
return runMarkdownBlockWithRenderer(input, extensions, renderer)
}
}
func doTestsBlock(t *testing.T, tests []string, extensions int) {
doTestsBlockWithRunner(t, tests, extensions, runMarkdownBlock)
}
func doTestsBlockWithRunner(t *testing.T, tests []string, extensions int, runner func(string, int) string) {
// catch and report panics
var candidate string
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
}
}()
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
candidate = input
expected := tests[i+1]
actual := runner(candidate, extensions)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
candidate = input[start:end]
_ = runMarkdownBlock(candidate, extensions)
}
}
}
}
}
func TestPrefixHeaderNoExtensions(t *testing.T) { func TestPrefixHeaderNoExtensions(t *testing.T) {
var tests = []string{ var tests = []string{
"# Header 1\n", "# Header 1\n",
@ -202,7 +144,7 @@ func TestPrefixHeaderSpaceExtension(t *testing.T) {
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" + "<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n", "<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
} }
doTestsBlock(t, tests, EXTENSION_SPACE_HEADERS) doTestsBlock(t, tests, SpaceHeadings)
} }
func TestPrefixHeaderIdExtension(t *testing.T) { func TestPrefixHeaderIdExtension(t *testing.T) {
@ -262,7 +204,7 @@ func TestPrefixHeaderIdExtension(t *testing.T) {
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" + "<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n", "<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
} }
doTestsBlock(t, tests, EXTENSION_HEADER_IDS) doTestsBlock(t, tests, HeadingIDs)
} }
func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
@ -305,12 +247,16 @@ func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
"<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n", "<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
} }
parameters := HtmlRendererParameters{ parameters := HTMLRendererParameters{
HeaderIDPrefix: "PRE:", HeadingIDPrefix: "PRE:",
HeaderIDSuffix: ":POST", HeadingIDSuffix: ":POST",
} }
doTestsBlockWithRunner(t, tests, EXTENSION_HEADER_IDS, runnerWithRendererParameters(parameters)) doTestsParam(t, tests, TestParams{
extensions: HeadingIDs,
HTMLFlags: UseXHTML,
HTMLRendererParameters: parameters,
})
} }
func TestPrefixAutoHeaderIdExtension(t *testing.T) { func TestPrefixAutoHeaderIdExtension(t *testing.T) {
@ -361,7 +307,7 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) {
"# Header\n\n# Header 1\n\n# Header\n\n# Header", "# Header\n\n# Header 1\n\n# Header\n\n# Header",
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n", "<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
} }
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) doTestsBlock(t, tests, AutoHeadingIDs)
} }
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) { func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
@ -413,12 +359,16 @@ func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
"<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n", "<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n",
} }
parameters := HtmlRendererParameters{ parameters := HTMLRendererParameters{
HeaderIDPrefix: "PRE:", HeadingIDPrefix: "PRE:",
HeaderIDSuffix: ":POST", HeadingIDSuffix: ":POST",
} }
doTestsBlockWithRunner(t, tests, EXTENSION_AUTO_HEADER_IDS, runnerWithRendererParameters(parameters)) doTestsParam(t, tests, TestParams{
extensions: AutoHeadingIDs,
HTMLFlags: UseXHTML,
HTMLRendererParameters: parameters,
})
} }
func TestPrefixMultipleHeaderExtensions(t *testing.T) { func TestPrefixMultipleHeaderExtensions(t *testing.T) {
@ -426,7 +376,7 @@ func TestPrefixMultipleHeaderExtensions(t *testing.T) {
"# Header\n\n# Header {#header}\n\n# Header 1", "# Header\n\n# Header {#header}\n\n# Header 1",
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n", "<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
} }
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS|EXTENSION_HEADER_IDS) doTestsBlock(t, tests, AutoHeadingIDs|HeadingIDs)
} }
func TestUnderlineHeaders(t *testing.T) { func TestUnderlineHeaders(t *testing.T) {
@ -526,7 +476,7 @@ func TestUnderlineHeadersAutoIDs(t *testing.T) {
"Header 1\n========\n\nHeader 1\n========\n", "Header 1\n========\n\nHeader 1\n========\n",
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n", "<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
} }
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS) doTestsBlock(t, tests, AutoHeadingIDs)
} }
func TestHorizontalRule(t *testing.T) { func TestHorizontalRule(t *testing.T) {
@ -900,7 +850,7 @@ func TestDefinitionList(t *testing.T) {
"</dl>\n" + "</dl>\n" +
"\n<p>Text 2</p>\n", "\n<p>Text 2</p>\n",
} }
doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS) doTestsBlock(t, tests, DefinitionLists)
} }
func TestPreformattedHtml(t *testing.T) { func TestPreformattedHtml(t *testing.T) {
@ -976,7 +926,7 @@ func TestPreformattedHtmlLax(t *testing.T) {
"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n", "Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n",
"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n", "<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
} }
doTestsBlock(t, tests, EXTENSION_LAX_HTML_BLOCKS) doTestsBlock(t, tests, LaxHTMLBlocks)
} }
func TestFencedCodeBlock(t *testing.T) { func TestFencedCodeBlock(t *testing.T) {
@ -1061,8 +1011,126 @@ func TestFencedCodeBlock(t *testing.T) {
"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block", "Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block",
"<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n", "<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n",
"```\n[]:()\n```\n",
"<pre><code>[]:()\n</code></pre>\n",
"```\n[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n```",
"<pre><code>[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n</code></pre>\n",
} }
doTestsBlock(t, tests, EXTENSION_FENCED_CODE) doTestsBlock(t, tests, FencedCode)
}
func TestFencedCodeInsideBlockquotes(t *testing.T) {
cat := func(s ...string) string { return strings.Join(s, "\n") }
var tests = []string{
cat("> ```go",
"package moo",
"",
"```",
""),
`<blockquote>
<pre><code class="language-go">package moo
</code></pre>
</blockquote>
`,
// -------------------------------------------
cat("> foo",
"> ",
"> ```go",
"package moo",
"```",
"> ",
"> goo.",
""),
`<blockquote>
<p>foo</p>
<pre><code class="language-go">package moo
</code></pre>
<p>goo.</p>
</blockquote>
`,
// -------------------------------------------
cat("> foo",
"> ",
"> quote",
"continues",
"```",
""),
`<blockquote>
<p>foo</p>
<p>quote
continues
` + "```" + `</p>
</blockquote>
`,
// -------------------------------------------
cat("> foo",
"> ",
"> ```go",
"package moo",
"```",
"> ",
"> goo.",
"> ",
"> ```go",
"package zoo",
"```",
"> ",
"> woo.",
""),
`<blockquote>
<p>foo</p>
<pre><code class="language-go">package moo
</code></pre>
<p>goo.</p>
<pre><code class="language-go">package zoo
</code></pre>
<p>woo.</p>
</blockquote>
`,
}
// These 2 alternative forms of blockquoted fenced code blocks should produce same output.
forms := [2]string{
cat("> plain quoted text",
"> ```fenced",
"code",
" with leading single space correctly preserved",
"okay",
"```",
"> rest of quoted text"),
cat("> plain quoted text",
"> ```fenced",
"> code",
"> with leading single space correctly preserved",
"> okay",
"> ```",
"> rest of quoted text"),
}
want := `<blockquote>
<p>plain quoted text</p>
<pre><code class="language-fenced">code
with leading single space correctly preserved
okay
</code></pre>
<p>rest of quoted text</p>
</blockquote>
`
tests = append(tests, forms[0], want)
tests = append(tests, forms[1], want)
doTestsBlock(t, tests, FencedCode)
} }
func TestTable(t *testing.T) { func TestTable(t *testing.T) {
@ -1109,7 +1177,7 @@ func TestTable(t *testing.T) {
"a|b\\|c|d\n---|---|---\nf|g\\|h|i\n", "a|b\\|c|d\n---|---|---\nf|g\\|h|i\n",
"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b|c</th>\n<th>d</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>f</td>\n<td>g|h</td>\n<td>i</td>\n</tr>\n</tbody>\n</table>\n", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b|c</th>\n<th>d</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>f</td>\n<td>g|h</td>\n<td>i</td>\n</tr>\n</tbody>\n</table>\n",
} }
doTestsBlock(t, tests, EXTENSION_TABLES) doTestsBlock(t, tests, Tables)
} }
func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
@ -1220,7 +1288,7 @@ func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
"* List\n\n * sublist\n\n normal text\n\n * another sublist\n", "* List\n\n * sublist\n\n normal text\n\n * another sublist\n",
"<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n", "<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n",
} }
doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) doTestsBlock(t, tests, NoEmptyLineBeforeBlock)
} }
func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
@ -1316,7 +1384,7 @@ func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
"1. numbers\n1. are ignored\n", "1. numbers\n1. are ignored\n",
"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n", "<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
} }
doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) doTestsBlock(t, tests, NoEmptyLineBeforeBlock)
} }
func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
@ -1387,7 +1455,7 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
" ``` oz\nleading spaces\n ```\n", " ``` oz\nleading spaces\n ```\n",
"<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces</p>\n\n<pre><code>```\n</code></pre>\n", "<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces</p>\n\n<pre><code>```\n</code></pre>\n",
} }
doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) doTestsBlock(t, tests, FencedCode|NoEmptyLineBeforeBlock)
} }
func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) { func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
@ -1398,10 +1466,226 @@ func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
"<h1 class=\"title\">" + "<h1 class=\"title\">" +
"Some title\n" + "Some title\n" +
"Another title line\n" + "Another title line\n" +
"Yep, more here too\n" + "Yep, more here too" +
"</h1>", "</h1>\n",
}
doTestsBlock(t, tests, Titleblock)
}
func TestBlockComments(t *testing.T) {
var tests = []string{
"Some text\n\n<!-- comment -->\n",
"<p>Some text</p>\n\n<!-- comment -->\n",
"Some text\n\n<!--\n\nmultiline\ncomment\n-->\n",
"<p>Some text</p>\n\n<!--\n\nmultiline\ncomment\n-->\n",
"Some text\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
"<p>Some text</p>\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
}
doTestsBlock(t, tests, 0)
}
func TestTOC(t *testing.T) {
var tests = []string{
"# Title\n\n##Subtitle1\n\n##Subtitle2",
//"<nav>\n<ul>\n<li><a href=\"#toc_0\">Title</a>\n<ul>\n<li><a href=\"#toc_1\">Subtitle1</a></li>\n<li><a href=\"#toc_2\">Subtitle2</a></li>\n</ul></li>\n</ul>\n</nav>\n\n<h1 id=\"toc_0\">Title</h1>\n\n<h2 id=\"toc_1\">Subtitle1</h2>\n\n<h2 id=\"toc_2\">Subtitle2</h2>\n",
`<nav>
<ul>
<li><a href="#toc_0">Title</a>
<ul>
<li><a href="#toc_1">Subtitle1</a></li>
<li><a href="#toc_2">Subtitle2</a></li>
</ul></li>
</ul>
</nav>
<h1 id="toc_0">Title</h1>
<h2 id="toc_1">Subtitle1</h2>
<h2 id="toc_2">Subtitle2</h2>
`,
"# Title\n\n##Subtitle\n\n#Title2",
//"<nav>\n<ul>\n<li><a href=\"#toc_0\">Title</a>\n<ul>\n<li><a href=\"#toc_1\">Subtitle</a></li>\n</ul></li>\n<li><a href=\"#toc_2\">Title2</a></li>\n</ul>\n</nav>\n\n<h1 id=\"toc_0\">Title</h1>\n\n<h2 id=\"toc_1\">Subtitle</h2>\n\n<h1 id=\"toc_2\">Title2</h1>\n",
`<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>
<h1 id="toc_0">Title</h1>
<h2 id="toc_1">Subtitle</h2>
<h1 id="toc_2">Title2</h1>
`,
"## Subtitle\n\n# Title",
`<nav>
<ul>
<li>
<ul>
<li><a href="#toc_0">Subtitle</a></li>
</ul></li>
<li><a href="#toc_1">Title</a></li>
</ul>
</nav>
<h2 id="toc_0">Subtitle</h2>
<h1 id="toc_1">Title</h1>
`,
"# Title 1\n\n## Subtitle 1\n\n### Subsubtitle 1\n\n# Title 2\n\n### Subsubtitle 2",
`<nav>
<ul>
<li><a href="#toc_0">Title 1</a>
<ul>
<li><a href="#toc_1">Subtitle 1</a>
<ul>
<li><a href="#toc_2">Subsubtitle 1</a></li>
</ul></li>
</ul></li>
<li><a href="#toc_3">Title 2</a>
<ul>
<li>
<ul>
<li><a href="#toc_4">Subsubtitle 2</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="toc_0">Title 1</h1>
<h2 id="toc_1">Subtitle 1</h2>
<h3 id="toc_2">Subsubtitle 1</h3>
<h1 id="toc_3">Title 2</h1>
<h3 id="toc_4">Subsubtitle 2</h3>
`,
"# Title with `code`",
`<nav>
<ul>
<li><a href="#toc_0">Title with <code>code</code></a></li>
</ul>
</nav>
<h1 id="toc_0">Title with <code>code</code></h1>
`,
// Trigger empty TOC
"#",
"",
}
doTestsParam(t, tests, TestParams{
HTMLFlags: UseXHTML | TOC,
})
}
func TestCompletePage(t *testing.T) {
var tests = []string{
"*foo*",
`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="GENERATOR" content="Blackfriday Markdown Processor v2.0" />
<meta charset="utf-8" />
</head>
<body>
<p><em>foo</em></p>
</body>
</html>
`,
}
doTestsParam(t, tests, TestParams{HTMLFlags: UseXHTML | CompletePage})
}
func TestIsFenceLine(t *testing.T) {
tests := []struct {
data []byte
syntaxRequested bool
wantEnd int
wantMarker string
wantSyntax string
}{
{
data: []byte("```"),
wantEnd: 3,
wantMarker: "```",
},
{
data: []byte("```\nstuff here\n"),
wantEnd: 4,
wantMarker: "```",
},
{
data: []byte("```\nstuff here\n"),
syntaxRequested: true,
wantEnd: 4,
wantMarker: "```",
},
{
data: []byte("stuff here\n```\n"),
wantEnd: 0,
},
{
data: []byte("```"),
syntaxRequested: true,
wantEnd: 3,
wantMarker: "```",
},
{
data: []byte("``` go"),
syntaxRequested: true,
wantEnd: 6,
wantMarker: "```",
wantSyntax: "go",
},
} }
doTestsBlock(t, tests, EXTENSION_TITLEBLOCK) for _, test := range tests {
var syntax *string
if test.syntaxRequested {
syntax = new(string)
}
end, marker := isFenceLine(test.data, syntax, "```")
if got, want := end, test.wantEnd; got != want {
t.Errorf("got end %v, want %v", got, want)
}
if got, want := marker, test.wantMarker; got != want {
t.Errorf("got marker %q, want %q", got, want)
}
if test.syntaxRequested {
if got, want := *syntax, test.wantSyntax; got != want {
t.Errorf("got syntax %q, want %q", got, want)
}
}
}
} }

18
vendor/github.com/documize/blackfriday/doc.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Package blackfriday is a markdown processor.
//
// It translates plain text with simple formatting rules into an AST, which can
// then be further processed to HTML (provided by Blackfriday itself) or other
// formats (provided by the community).
//
// The simplest way to invoke Blackfriday is to call the Run function. It will
// take a text input and produce a text output in HTML (or other format).
//
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
// processor and to call Parse, which returns a syntax tree for the input
// document. You can leverage Blackfriday's parsing for content extraction from
// markdown documents. You can assign a custom renderer and set various options
// to the Markdown processor.
//
// If you're interested in calling Blackfriday from command line, see
// https://github.com/russross/blackfriday-tool.
package blackfriday

34
vendor/github.com/documize/blackfriday/esc.go generated vendored Normal file
View file

@ -0,0 +1,34 @@
package blackfriday
import (
"html"
"io"
)
var htmlEscaper = [256][]byte{
'&': []byte("&amp;"),
'<': []byte("&lt;"),
'>': []byte("&gt;"),
'"': []byte("&quot;"),
}
func escapeHTML(w io.Writer, s []byte) {
var start, end int
for end < len(s) {
escSeq := htmlEscaper[s[end]]
if escSeq != nil {
w.Write(s[start:end])
w.Write(escSeq)
start = end + 1
}
end++
}
if start < len(s) && end <= len(s) {
w.Write(s[start:end])
}
}
func escLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
escapeHTML(w, []byte(unesc))
}

48
vendor/github.com/documize/blackfriday/esc_test.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
package blackfriday
import (
"bytes"
"testing"
)
func TestEsc(t *testing.T) {
tests := []string{
"abc", "abc",
"a&c", "a&amp;c",
"<", "&lt;",
"[]:<", "[]:&lt;",
"Hello <!--", "Hello &lt;!--",
}
for i := 0; i < len(tests); i += 2 {
var b bytes.Buffer
escapeHTML(&b, []byte(tests[i]))
if !bytes.Equal(b.Bytes(), []byte(tests[i+1])) {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
tests[i], tests[i+1], b.String())
}
}
}
func BenchmarkEscapeHTML(b *testing.B) {
tests := [][]byte{
[]byte(""),
[]byte("AT&T has an ampersand in their name."),
[]byte("AT&amp;T is another way to write it."),
[]byte("This & that."),
[]byte("4 < 5."),
[]byte("6 > 5."),
[]byte("Here's a [link] [1] with an ampersand in the URL."),
[]byte("Here's a link with an ampersand in the link text: [AT&T] [2]."),
[]byte("Here's an inline [link](/script?foo=1&bar=2)."),
[]byte("Here's an inline [link](</script?foo=1&bar=2>)."),
[]byte("[1]: http://example.com/?foo=1&bar=2"),
[]byte("[2]: http://att.com/ \"AT&T\""),
}
var buf bytes.Buffer
for n := 0; n < b.N; n++ {
for _, t := range tests {
escapeHTML(&buf, t)
buf.Reset()
}
}
}

186
vendor/github.com/documize/blackfriday/helpers_test.go generated vendored Normal file
View file

@ -0,0 +1,186 @@
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
// Helper functions for unit testing
//
package blackfriday
import (
"io/ioutil"
"path/filepath"
"regexp"
"testing"
)
type TestParams struct {
extensions Extensions
referenceOverride ReferenceOverrideFunc
HTMLFlags
HTMLRendererParameters
}
func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) {
// Catch and report panics. This is useful when running 'go test -v' on
// the integration server. When developing, though, crash dump is often
// preferable, so recovery can be easily turned off with doRecover = false.
var candidate string
const doRecover = true
if doRecover {
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
}
}()
}
suite(&candidate)
}
func runMarkdown(input string, params TestParams) string {
params.HTMLRendererParameters.Flags = params.HTMLFlags
renderer := NewHTMLRenderer(params.HTMLRendererParameters)
return string(Run([]byte(input), WithRenderer(renderer),
WithExtensions(params.extensions),
WithRefOverride(params.referenceOverride)))
}
// doTests runs full document tests using MarkdownCommon configuration.
func doTests(t *testing.T, tests []string) {
doTestsParam(t, tests, TestParams{
extensions: CommonExtensions,
HTMLRendererParameters: HTMLRendererParameters{
Flags: CommonHTMLFlags,
},
})
}
func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
doTestsParam(t, tests, TestParams{
extensions: extensions,
HTMLFlags: UseXHTML,
})
}
func doTestsParam(t *testing.T, tests []string, params TestParams) {
execRecoverableTestSuite(t, tests, params, func(candidate *string) {
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
*candidate = input
expected := tests[i+1]
actual := runMarkdown(*candidate, params)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
*candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
*candidate = input[start:end]
runMarkdown(*candidate, params)
}
}
}
}
})
}
func doTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, TestParams{})
}
func doLinkTestsInline(t *testing.T, tests []string) {
doTestsInline(t, tests)
prefix := "http://localhost"
params := HTMLRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, TestParams{
HTMLRendererParameters: params,
})
doTestsInlineParam(t, transformTests, TestParams{
HTMLFlags: UseXHTML,
HTMLRendererParameters: params,
})
}
func doSafeTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Safelink})
// All the links in this test should not have the prefix appended, so
// just rerun it with different parameters and the same expectations.
prefix := "http://localhost"
params := HTMLRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, TestParams{
HTMLFlags: Safelink,
HTMLRendererParameters: params,
})
}
func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
params.extensions |= Autolink | Strikethrough
params.HTMLFlags |= UseXHTML
doTestsParam(t, tests, params)
}
func transformLinks(tests []string, prefix string) []string {
newTests := make([]string, len(tests))
anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
for i, test := range tests {
if i%2 == 1 {
test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
}
newTests[i] = test
}
return newTests
}
func doTestsReference(t *testing.T, files []string, flag Extensions) {
params := TestParams{extensions: flag}
execRecoverableTestSuite(t, files, params, func(candidate *string) {
for _, basename := range files {
filename := filepath.Join("testdata", basename+".text")
inputBytes, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
continue
}
input := string(inputBytes)
filename = filepath.Join("testdata", basename+".html")
expectedBytes, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
continue
}
expected := string(expectedBytes)
actual := string(runMarkdown(input, params))
if actual != expected {
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
basename+".text", expected, actual)
}
// now test every prefix of every input to check for
// bounds checking
if !testing.Short() {
start, max := 0, len(input)
for end := start + 1; end <= max; end++ {
*candidate = input[start:end]
runMarkdown(*candidate, params)
}
}
}
})
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -20,90 +20,6 @@ import (
"strings" "strings"
) )
func runMarkdownInline(input string, opts Options, htmlFlags int, params HtmlRendererParameters) string {
opts.Extensions |= EXTENSION_AUTOLINK
opts.Extensions |= EXTENSION_STRIKETHROUGH
htmlFlags |= HTML_USE_XHTML
renderer := HtmlRendererWithParameters(htmlFlags, "", "", params)
return string(MarkdownOptions([]byte(input), renderer, opts))
}
func doTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, Options{}, 0, HtmlRendererParameters{})
}
func doLinkTestsInline(t *testing.T, tests []string) {
doTestsInline(t, tests)
prefix := "http://localhost"
params := HtmlRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, Options{}, 0, params)
doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params)
}
func doSafeTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK, HtmlRendererParameters{})
// All the links in this test should not have the prefix appended, so
// just rerun it with different parameters and the same expectations.
prefix := "http://localhost"
params := HtmlRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, Options{}, HTML_SAFELINK, params)
}
func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags int,
params HtmlRendererParameters) {
// catch and report panics
var candidate string
/*
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v] (%v)\n", candidate, err)
}
}()
*/
for i := 0; i+1 < len(tests); i += 2 {
input := tests[i]
candidate = input
expected := tests[i+1]
actual := runMarkdownInline(candidate, opts, htmlFlags, params)
if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual)
}
// now test every substring to stress test bounds checking
if !testing.Short() {
for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ {
candidate = input[start:end]
_ = runMarkdownInline(candidate, opts, htmlFlags, params)
}
}
}
}
}
func transformLinks(tests []string, prefix string) []string {
newTests := make([]string, len(tests))
anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
for i, test := range tests {
if i%2 == 1 {
test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
}
newTests[i] = test
}
return newTests
}
func TestEmphasis(t *testing.T) { func TestEmphasis(t *testing.T) {
var tests = []string{ var tests = []string{
"nothing inline\n", "nothing inline\n",
@ -153,6 +69,9 @@ func TestEmphasis(t *testing.T) {
"mix of *markers_\n", "mix of *markers_\n",
"<p>mix of *markers_</p>\n", "<p>mix of *markers_</p>\n",
"*What is A\\* algorithm?*\n",
"<p><em>What is A* algorithm?</em></p>\n",
} }
doTestsInline(t, tests) doTestsInline(t, tests)
} }
@ -180,11 +99,11 @@ func TestReferenceOverride(t *testing.T) {
"test [ref5][]\n", "test [ref5][]\n",
"<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n", "<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n",
} }
doTestsInlineParam(t, tests, Options{ doTestsInlineParam(t, tests, TestParams{
ReferenceOverride: func(reference string) (rv *Reference, overridden bool) { referenceOverride: func(reference string) (rv *Reference, overridden bool) {
switch reference { switch reference {
case "ref1": case "ref1":
// just an overriden reference exists without definition // just an overridden reference exists without definition
return &Reference{ return &Reference{
Link: "http://www.ref1.com/", Link: "http://www.ref1.com/",
Title: "Reference 1"}, true Title: "Reference 1"}, true
@ -211,7 +130,8 @@ func TestReferenceOverride(t *testing.T) {
}, true }, true
} }
return nil, false return nil, false
}}, 0, HtmlRendererParameters{}) },
})
} }
func TestStrong(t *testing.T) { func TestStrong(t *testing.T) {
@ -263,6 +183,12 @@ func TestStrong(t *testing.T) {
"mix of **markers__\n", "mix of **markers__\n",
"<p>mix of **markers__</p>\n", "<p>mix of **markers__</p>\n",
"**`/usr`** : this folder is named `usr`\n",
"<p><strong><code>/usr</code></strong> : this folder is named <code>usr</code></p>\n",
"**`/usr`** :\n\n this folder is named `usr`\n",
"<p><strong><code>/usr</code></strong> :</p>\n\n<p>this folder is named <code>usr</code></p>\n",
} }
doTestsInline(t, tests) doTestsInline(t, tests)
} }
@ -291,7 +217,7 @@ func TestEmphasisMix(t *testing.T) {
"<p><strong>improper *nesting</strong> is* bad</p>\n", "<p><strong>improper *nesting</strong> is* bad</p>\n",
"*improper **nesting* is** bad\n", "*improper **nesting* is** bad\n",
"<p><em>improper **nesting</em> is** bad</p>\n", "<p>*improper <strong>nesting* is</strong> bad</p>\n",
} }
doTestsInline(t, tests) doTestsInline(t, tests)
} }
@ -415,9 +341,8 @@ func TestLineBreak(t *testing.T) {
"this has an \nextra space\n", "this has an \nextra space\n",
"<p>this has an<br />\nextra space</p>\n", "<p>this has an<br />\nextra space</p>\n",
} }
doTestsInlineParam(t, tests, Options{ doTestsInlineParam(t, tests, TestParams{
Extensions: EXTENSION_BACKSLASH_LINE_BREAK}, extensions: BackslashLineBreak})
0, HtmlRendererParameters{})
} }
func TestInlineLink(t *testing.T) { func TestInlineLink(t *testing.T) {
@ -557,8 +482,9 @@ func TestRelAttrLink(t *testing.T) {
"[foo](../bar)\n", "[foo](../bar)\n",
"<p><a href=\"../bar\">foo</a></p>\n", "<p><a href=\"../bar\">foo</a></p>\n",
} }
doTestsInlineParam(t, nofollowTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, doTestsInlineParam(t, nofollowTests, TestParams{
HtmlRendererParameters{}) HTMLFlags: Safelink | NofollowLinks,
})
var noreferrerTests = []string{ var noreferrerTests = []string{
"[foo](http://bar.com/foo/)\n", "[foo](http://bar.com/foo/)\n",
@ -567,8 +493,9 @@ func TestRelAttrLink(t *testing.T) {
"[foo](/bar/)\n", "[foo](/bar/)\n",
"<p><a href=\"/bar/\">foo</a></p>\n", "<p><a href=\"/bar/\">foo</a></p>\n",
} }
doTestsInlineParam(t, noreferrerTests, Options{}, HTML_SAFELINK|HTML_NOREFERRER_LINKS, doTestsInlineParam(t, noreferrerTests, TestParams{
HtmlRendererParameters{}) HTMLFlags: Safelink | NoreferrerLinks,
})
var nofollownoreferrerTests = []string{ var nofollownoreferrerTests = []string{
"[foo](http://bar.com/foo/)\n", "[foo](http://bar.com/foo/)\n",
@ -577,8 +504,9 @@ func TestRelAttrLink(t *testing.T) {
"[foo](/bar/)\n", "[foo](/bar/)\n",
"<p><a href=\"/bar/\">foo</a></p>\n", "<p><a href=\"/bar/\">foo</a></p>\n",
} }
doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS, doTestsInlineParam(t, nofollownoreferrerTests, TestParams{
HtmlRendererParameters{}) HTMLFlags: Safelink | NofollowLinks | NoreferrerLinks,
})
} }
func TestHrefTargetBlank(t *testing.T) { func TestHrefTargetBlank(t *testing.T) {
@ -605,7 +533,9 @@ func TestHrefTargetBlank(t *testing.T) {
"[foo](http://example.com)\n", "[foo](http://example.com)\n",
"<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n", "<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n",
} }
doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{}) doTestsInlineParam(t, tests, TestParams{
HTMLFlags: Safelink | HrefTargetBlank,
})
} }
func TestSafeInlineLink(t *testing.T) { func TestSafeInlineLink(t *testing.T) {
@ -672,6 +602,9 @@ func TestReferenceLink(t *testing.T) {
"[ref]\n [ref]: ../url/ \"title\"\n", "[ref]\n [ref]: ../url/ \"title\"\n",
"<p><a href=\"../url/\" title=\"title\">ref</a></p>\n", "<p><a href=\"../url/\" title=\"title\">ref</a></p>\n",
"[link][ref]\n [ref]: /url/",
"<p><a href=\"/url/\">link</a></p>\n",
} }
doLinkTestsInline(t, tests) doLinkTestsInline(t, tests)
} }
@ -799,6 +732,9 @@ func TestAutoLink(t *testing.T) {
"http://foo.com/viewtopic.php?param=&quot;18&quot;", "http://foo.com/viewtopic.php?param=&quot;18&quot;",
"<p><a href=\"http://foo.com/viewtopic.php?param=&quot;18&quot;\">http://foo.com/viewtopic.php?param=&quot;18&quot;</a></p>\n", "<p><a href=\"http://foo.com/viewtopic.php?param=&quot;18&quot;\">http://foo.com/viewtopic.php?param=&quot;18&quot;</a></p>\n",
"<a href=\"https://fancy.com\">https://fancy.com</a>\n",
"<p><a href=\"https://fancy.com\">https://fancy.com</a></p>\n",
} }
doLinkTestsInline(t, tests) doLinkTestsInline(t, tests)
} }
@ -806,14 +742,15 @@ func TestAutoLink(t *testing.T) {
var footnoteTests = []string{ var footnoteTests = []string{
"testing footnotes.[^a]\n\n[^a]: This is the note\n", "testing footnotes.[^a]\n\n[^a]: This is the note\n",
`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p> `<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
<ol> <ol>
<li id="fn:a">This is the note <li id="fn:a">This is the note</li>
</li>
</ol> </ol>
</div> </div>
`, `,
@ -832,6 +769,7 @@ No longer in the footnote
`<p>testing long<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">1</a></sup> notes.</p> `<p>testing long<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">1</a></sup> notes.</p>
<p>No longer in the footnote</p> <p>No longer in the footnote</p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
@ -845,9 +783,9 @@ No longer in the footnote
some code some code
</code></p> </code></p>
<p>Paragraph 3</p> <p>Paragraph 3</p></li>
</li>
</ol> </ol>
</div> </div>
`, `,
@ -870,21 +808,23 @@ what happens here
<p>omg</p> <p>omg</p>
<p>what happens here</p> <p>what happens here</p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
<ol> <ol>
<li id="fn:c">this is <a href="/link/c">note</a> c <li id="fn:c">this is <a href="/link/c">note</a> c</li>
</li>
<li id="fn:d">this is note d <li id="fn:d">this is note d</li>
</li>
</ol> </ol>
</div> </div>
`, `,
"testing inline^[this is the note] notes.\n", "testing inline^[this is the note] notes.\n",
`<p>testing inline<sup class="footnote-ref" id="fnref:this-is-the-note"><a rel="footnote" href="#fn:this-is-the-note">1</a></sup> notes.</p> `<p>testing inline<sup class="footnote-ref" id="fnref:this-is-the-note"><a rel="footnote" href="#fn:this-is-the-note">1</a></sup> notes.</p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
@ -892,11 +832,13 @@ what happens here
<ol> <ol>
<li id="fn:this-is-the-note">this is the note</li> <li id="fn:this-is-the-note">this is the note</li>
</ol> </ol>
</div> </div>
`, `,
"testing multiple[^1] types^[inline note] of notes[^2]\n\n[^2]: the second deferred note\n[^1]: the first deferred note\n\n\twhich happens to be a block\n", "testing multiple[^1] types^[inline note] of notes[^2]\n\n[^2]: the second deferred note\n[^1]: the first deferred note\n\n\twhich happens to be a block\n",
`<p>testing multiple<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> types<sup class="footnote-ref" id="fnref:inline-note"><a rel="footnote" href="#fn:inline-note">2</a></sup> of notes<sup class="footnote-ref" id="fnref:2"><a rel="footnote" href="#fn:2">3</a></sup></p> `<p>testing multiple<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> types<sup class="footnote-ref" id="fnref:inline-note"><a rel="footnote" href="#fn:inline-note">2</a></sup> of notes<sup class="footnote-ref" id="fnref:2"><a rel="footnote" href="#fn:2">3</a></sup></p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
@ -904,12 +846,13 @@ what happens here
<ol> <ol>
<li id="fn:1"><p>the first deferred note</p> <li id="fn:1"><p>the first deferred note</p>
<p>which happens to be a block</p> <p>which happens to be a block</p></li>
</li>
<li id="fn:inline-note">inline note</li> <li id="fn:inline-note">inline note</li>
<li id="fn:2">the second deferred note
</li> <li id="fn:2">the second deferred note</li>
</ol> </ol>
</div> </div>
`, `,
@ -920,6 +863,7 @@ what happens here
may be multiple paragraphs. may be multiple paragraphs.
`, `,
`<p>This is a footnote<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup><sup class="footnote-ref" id="fnref:and-this-is-an-i"><a rel="footnote" href="#fn:and-this-is-an-i">2</a></sup></p> `<p>This is a footnote<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup><sup class="footnote-ref" id="fnref:and-this-is-an-i"><a rel="footnote" href="#fn:and-this-is-an-i">2</a></sup></p>
<div class="footnotes"> <div class="footnotes">
<hr /> <hr />
@ -927,25 +871,70 @@ what happens here
<ol> <ol>
<li id="fn:1"><p>the footnote text.</p> <li id="fn:1"><p>the footnote text.</p>
<p>may be multiple paragraphs.</p> <p>may be multiple paragraphs.</p></li>
</li>
<li id="fn:and-this-is-an-i">and this is an inline footnote</li> <li id="fn:and-this-is-an-i">and this is an inline footnote</li>
</ol> </ol>
</div> </div>
`, `,
"empty footnote[^]\n\n[^]: fn text", "empty footnote[^]\n\n[^]: fn text",
"<p>empty footnote<sup class=\"footnote-ref\" id=\"fnref:\"><a rel=\"footnote\" href=\"#fn:\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:\">fn text\n</li>\n</ol>\n</div>\n", "<p>empty footnote<sup class=\"footnote-ref\" id=\"fnref:\"><a rel=\"footnote\" href=\"#fn:\">1</a></sup></p>\n\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:\">fn text</li>\n</ol>\n\n</div>\n",
"Some text.[^note1]\n\n[^note1]: fn1", "Some text.[^note1]\n\n[^note1]: fn1",
"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n</ol>\n</div>\n", "<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup></p>\n\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1</li>\n</ol>\n\n</div>\n",
"Some text.[^note1][^note2]\n\n[^note1]: fn1\n[^note2]: fn2\n", "Some text.[^note1][^note2]\n\n[^note1]: fn1\n[^note2]: fn2\n",
"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup><sup class=\"footnote-ref\" id=\"fnref:note2\"><a rel=\"footnote\" href=\"#fn:note2\">2</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n<li id=\"fn:note2\">fn2\n</li>\n</ol>\n</div>\n", "<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup><sup class=\"footnote-ref\" id=\"fnref:note2\"><a rel=\"footnote\" href=\"#fn:note2\">2</a></sup></p>\n\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1</li>\n\n<li id=\"fn:note2\">fn2</li>\n</ol>\n\n</div>\n",
`Bla bla [^1] [WWW][w3]
[^1]: This is a footnote
[w3]: http://www.w3.org/
`,
`<p>Bla bla <sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> <a href="http://www.w3.org/">WWW</a></p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">This is a footnote</li>
</ol>
</div>
`,
`This is exciting![^fn1]
[^fn1]: Fine print
`,
`<p>This is exciting!<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:fn1">Fine print</li>
</ol>
</div>
`,
`This text does not reference a footnote.
[^footnote]: But it has a footnote! And it gets omitted.
`,
"<p>This text does not reference a footnote.</p>\n",
} }
func TestFootnotes(t *testing.T) { func TestFootnotes(t *testing.T) {
doTestsInlineParam(t, footnoteTests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, HtmlRendererParameters{}) doTestsInlineParam(t, footnoteTests, TestParams{
extensions: Footnotes,
})
} }
func TestFootnotesWithParameters(t *testing.T) { func TestFootnotesWithParameters(t *testing.T) {
@ -965,12 +954,72 @@ func TestFootnotesWithParameters(t *testing.T) {
tests[i] = test tests[i] = test
} }
params := HtmlRendererParameters{ params := HTMLRendererParameters{
FootnoteAnchorPrefix: prefix, FootnoteAnchorPrefix: prefix,
FootnoteReturnLinkContents: returnText, FootnoteReturnLinkContents: returnText,
} }
doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, HTML_FOOTNOTE_RETURN_LINKS, params) doTestsInlineParam(t, tests, TestParams{
extensions: Footnotes,
HTMLFlags: FootnoteReturnLinks,
HTMLRendererParameters: params,
})
}
func TestNestedFootnotes(t *testing.T) {
var tests = []string{
`Paragraph.[^fn1]
[^fn1]:
Asterisk[^fn2]
[^fn2]:
Obelisk`,
`<p>Paragraph.<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:fn1">Asterisk<sup class="footnote-ref" id="fnref:fn2"><a rel="footnote" href="#fn:fn2">2</a></sup></li>
<li id="fn:fn2">Obelisk</li>
</ol>
</div>
`,
}
doTestsInlineParam(t, tests, TestParams{extensions: Footnotes})
}
func TestInlineComments(t *testing.T) {
var tests = []string{
"Hello <!-- there ->\n",
"<p>Hello &lt;!&mdash; there &ndash;&gt;</p>\n",
"Hello <!-- there -->\n",
"<p>Hello <!-- there --></p>\n",
"Hello <!-- there -->",
"<p>Hello <!-- there --></p>\n",
"Hello <!---->\n",
"<p>Hello <!----></p>\n",
"Hello <!-- there -->\na",
"<p>Hello <!-- there -->\na</p>\n",
"* list <!-- item -->\n",
"<ul>\n<li>list <!-- item --></li>\n</ul>\n",
"<!-- Front --> comment\n",
"<p><!-- Front --> comment</p>\n",
"blahblah\n<!--- foo -->\nrhubarb\n",
"<p>blahblah\n<!--- foo -->\nrhubarb</p>\n",
}
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsDashes})
} }
func TestSmartDoubleQuotes(t *testing.T) { func TestSmartDoubleQuotes(t *testing.T) {
@ -982,7 +1031,19 @@ func TestSmartDoubleQuotes(t *testing.T) {
"two pair of \"some\" quoted \"text\".\n", "two pair of \"some\" quoted \"text\".\n",
"<p>two pair of &ldquo;some&rdquo; quoted &ldquo;text&rdquo;.</p>\n"} "<p>two pair of &ldquo;some&rdquo; quoted &ldquo;text&rdquo;.</p>\n"}
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants})
}
func TestSmartDoubleQuotesNBSP(t *testing.T) {
var tests = []string{
"this should be normal \"quoted\" text.\n",
"<p>this should be normal &ldquo;&nbsp;quoted&nbsp;&rdquo; text.</p>\n",
"this \" single double\n",
"<p>this &ldquo;&nbsp; single double</p>\n",
"two pair of \"some\" quoted \"text\".\n",
"<p>two pair of &ldquo;&nbsp;some&nbsp;&rdquo; quoted &ldquo;&nbsp;text&nbsp;&rdquo;.</p>\n"}
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsQuotesNBSP})
} }
func TestSmartAngledDoubleQuotes(t *testing.T) { func TestSmartAngledDoubleQuotes(t *testing.T) {
@ -994,7 +1055,19 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
"two pair of \"some\" quoted \"text\".\n", "two pair of \"some\" quoted \"text\".\n",
"<p>two pair of &laquo;some&raquo; quoted &laquo;text&raquo;.</p>\n"} "<p>two pair of &laquo;some&raquo; quoted &laquo;text&raquo;.</p>\n"}
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{}) doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes})
}
func TestSmartAngledDoubleQuotesNBSP(t *testing.T) {
var tests = []string{
"this should be angled \"quoted\" text.\n",
"<p>this should be angled &laquo;&nbsp;quoted&nbsp;&raquo; text.</p>\n",
"this \" single double\n",
"<p>this &laquo;&nbsp; single double</p>\n",
"two pair of \"some\" quoted \"text\".\n",
"<p>two pair of &laquo;&nbsp;some&nbsp;&raquo; quoted &laquo;&nbsp;text&nbsp;&raquo;.</p>\n"}
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes | SmartypantsQuotesNBSP})
} }
func TestSmartFractions(t *testing.T) { func TestSmartFractions(t *testing.T) {
@ -1004,7 +1077,7 @@ func TestSmartFractions(t *testing.T) {
"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n", "1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n",
"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"} "<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants})
tests = []string{ tests = []string{
"1/2, 2/3, 81/100 and 1000000/1048576.\n", "1/2, 2/3, 81/100 and 1000000/1048576.\n",
@ -1012,5 +1085,92 @@ func TestSmartFractions(t *testing.T) {
"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n", "1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n",
"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"} "<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsFractions})
}
func TestDisableSmartDashes(t *testing.T) {
doTestsInlineParam(t, []string{
"foo - bar\n",
"<p>foo - bar</p>\n",
"foo -- bar\n",
"<p>foo -- bar</p>\n",
"foo --- bar\n",
"<p>foo --- bar</p>\n",
}, TestParams{})
doTestsInlineParam(t, []string{
"foo - bar\n",
"<p>foo &ndash; bar</p>\n",
"foo -- bar\n",
"<p>foo &mdash; bar</p>\n",
"foo --- bar\n",
"<p>foo &mdash;&ndash; bar</p>\n",
}, TestParams{HTMLFlags: Smartypants | SmartypantsDashes})
doTestsInlineParam(t, []string{
"foo - bar\n",
"<p>foo - bar</p>\n",
"foo -- bar\n",
"<p>foo &ndash; bar</p>\n",
"foo --- bar\n",
"<p>foo &mdash; bar</p>\n",
}, TestParams{HTMLFlags: Smartypants | SmartypantsLatexDashes | SmartypantsDashes})
doTestsInlineParam(t, []string{
"foo - bar\n",
"<p>foo - bar</p>\n",
"foo -- bar\n",
"<p>foo -- bar</p>\n",
"foo --- bar\n",
"<p>foo --- bar</p>\n",
}, TestParams{HTMLFlags: Smartypants | SmartypantsLatexDashes})
}
func TestSkipLinks(t *testing.T) {
doTestsInlineParam(t, []string{
"[foo](gopher://foo.bar)",
"<p><tt>foo</tt></p>\n",
"[foo](mailto://bar/)\n",
"<p><tt>foo</tt></p>\n",
}, TestParams{
HTMLFlags: SkipLinks,
})
}
func TestSkipImages(t *testing.T) {
doTestsInlineParam(t, []string{
"![foo](/bar/)\n",
"<p></p>\n",
}, TestParams{
HTMLFlags: SkipImages,
})
}
func TestUseXHTML(t *testing.T) {
doTestsParam(t, []string{
"---",
"<hr>\n",
}, TestParams{})
doTestsParam(t, []string{
"---",
"<hr />\n",
}, TestParams{HTMLFlags: UseXHTML})
}
func TestSkipHTML(t *testing.T) {
doTestsParam(t, []string{
"<div class=\"foo\"></div>\n\ntext\n\n<form>the form</form>",
"<p>text</p>\n\n<p>the form</p>\n",
"text <em>inline html</em> more text",
"<p>text inline html more text</p>\n",
}, TestParams{HTMLFlags: SkipHTML})
}
func BenchmarkSmartDoubleQuotes(b *testing.B) {
params := TestParams{HTMLFlags: Smartypants}
params.extensions |= Autolink | Strikethrough
params.HTMLFlags |= UseXHTML
for i := 0; i < b.N; i++ {
runMarkdown("this should be normal \"quoted\" text.\n", params)
}
} }

View file

@ -1,332 +0,0 @@
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
//
// LaTeX rendering backend
//
//
package blackfriday
import (
"bytes"
)
// Latex is a type that implements the Renderer interface for LaTeX output.
//
// Do not create this directly, instead use the LatexRenderer function.
type Latex struct {
}
// LatexRenderer 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 LatexRenderer(flags int) Renderer {
return &Latex{}
}
func (options *Latex) GetFlags() int {
return 0
}
// render code chunks using verbatim, or listings if we have a language
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
if lang == "" {
out.WriteString("\n\\begin{verbatim}\n")
} else {
out.WriteString("\n\\begin{lstlisting}[language=")
out.WriteString(lang)
out.WriteString("]\n")
}
out.Write(text)
if lang == "" {
out.WriteString("\n\\end{verbatim}\n")
} else {
out.WriteString("\n\\end{lstlisting}\n")
}
}
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
}
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
out.WriteString("\n\\begin{quotation}\n")
out.Write(text)
out.WriteString("\n\\end{quotation}\n")
}
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
// a pretty lame thing to do...
out.WriteString("\n\\begin{verbatim}\n")
out.Write(text)
out.WriteString("\n\\end{verbatim}\n")
}
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
marker := out.Len()
switch level {
case 1:
out.WriteString("\n\\section{")
case 2:
out.WriteString("\n\\subsection{")
case 3:
out.WriteString("\n\\subsubsection{")
case 4:
out.WriteString("\n\\paragraph{")
case 5:
out.WriteString("\n\\subparagraph{")
case 6:
out.WriteString("\n\\textbf{")
}
if !text() {
out.Truncate(marker)
return
}
out.WriteString("}\n")
}
func (options *Latex) HRule(out *bytes.Buffer) {
out.WriteString("\n\\HRule\n")
}
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len()
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\begin{enumerate}\n")
} else {
out.WriteString("\n\\begin{itemize}\n")
}
if !text() {
out.Truncate(marker)
return
}
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\end{enumerate}\n")
} else {
out.WriteString("\n\\end{itemize}\n")
}
}
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
out.WriteString("\n\\item ")
out.Write(text)
}
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
marker := out.Len()
out.WriteString("\n")
if !text() {
out.Truncate(marker)
return
}
out.WriteString("\n")
}
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
out.WriteString("\n\\begin{tabular}{")
for _, elt := range columnData {
switch elt {
case TABLE_ALIGNMENT_LEFT:
out.WriteByte('l')
case TABLE_ALIGNMENT_RIGHT:
out.WriteByte('r')
default:
out.WriteByte('c')
}
}
out.WriteString("}\n")
out.Write(header)
out.WriteString(" \\\\\n\\hline\n")
out.Write(body)
out.WriteString("\n\\end{tabular}\n")
}
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
if out.Len() > 0 {
out.WriteString(" \\\\\n")
}
out.Write(text)
}
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
// TODO: this
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
}
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
}
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
out.WriteString("\\href{")
if kind == LINK_TYPE_EMAIL {
out.WriteString("mailto:")
}
out.Write(link)
out.WriteString("}{")
out.Write(link)
out.WriteString("}")
}
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("\\texttt{")
escapeSpecialChars(out, text)
out.WriteString("}")
}
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textit{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
// treat it like a link
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(alt)
out.WriteString("}")
} else {
out.WriteString("\\includegraphics{")
out.Write(link)
out.WriteString("}")
}
}
func (options *Latex) LineBreak(out *bytes.Buffer) {
out.WriteString(" \\\\\n")
}
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(content)
out.WriteString("}")
}
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
}
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{\\textit{")
out.Write(text)
out.WriteString("}}")
}
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
out.WriteString("\\sout{")
out.Write(text)
out.WriteString("}")
}
// TODO: this
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
}
func needsBackslash(c byte) bool {
for _, r := range []byte("_{}%$&\\~#") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(out *bytes.Buffer, 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 {
out.Write(text[org:i])
}
// escape a character
if i >= len(text) {
break
}
out.WriteByte('\\')
out.WriteByte(text[i])
}
}
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
// TODO: convert this into a unicode character or something
out.Write(entity)
}
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
escapeSpecialChars(out, text)
}
// header and footer
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
out.WriteString("\\documentclass{article}\n")
out.WriteString("\n")
out.WriteString("\\usepackage{graphicx}\n")
out.WriteString("\\usepackage{listings}\n")
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
out.WriteString("\\usepackage[utf8]{inputenc}\n")
out.WriteString("\\usepackage{verbatim}\n")
out.WriteString("\\usepackage[normalem]{ulem}\n")
out.WriteString("\\usepackage{hyperref}\n")
out.WriteString("\n")
out.WriteString("\\hypersetup{colorlinks,%\n")
out.WriteString(" citecolor=black,%\n")
out.WriteString(" filecolor=black,%\n")
out.WriteString(" linkcolor=black,%\n")
out.WriteString(" urlcolor=black,%\n")
out.WriteString(" pdfstartview=FitH,%\n")
out.WriteString(" breaklinks=true,%\n")
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
out.WriteString(VERSION)
out.WriteString("}}\n")
out.WriteString("\n")
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
out.WriteString("\\parindent=0pt\n")
out.WriteString("\n")
out.WriteString("\\begin{document}\n")
}
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
out.WriteString("\n\\end{document}\n")
}

View file

@ -1,225 +1,200 @@
//
// Blackfriday Markdown Processor // Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday // Available at http://github.com/russross/blackfriday
// //
// Copyright © 2011 Russ Ross <russ@russross.com>. // Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License. // Distributed under the Simplified BSD License.
// See README.md for details. // See README.md for details.
//
//
//
// Markdown parsing and processing
//
//
// Blackfriday markdown processor.
//
// Translates plain text with simple formatting rules into HTML or LaTeX.
package blackfriday package blackfriday
import ( import (
"bytes" "bytes"
"fmt"
"io"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
const VERSION = "1.1" //
// Markdown parsing and processing
//
// Version string of the package. Appears in the rendered document when
// CompletePage flag is on.
const Version = "2.0"
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
// extensions.
type Extensions int
// These are the supported markdown parsing extensions. // These are the supported markdown parsing extensions.
// OR these values together to select multiple extensions. // OR these values together to select multiple extensions.
const ( const (
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words NoExtensions Extensions = 0
EXTENSION_TABLES // render tables NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
EXTENSION_FENCED_CODE // render fenced code blocks Tables // Render tables
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked FencedCode // Render fenced code blocks
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~ Autolink // Detect embedded URLs that are not explicitly marked
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules Strikethrough // Strikethrough text using ~~test~~
EXTENSION_SPACE_HEADERS // be strict about prefix header rules LaxHTMLBlocks // Loosen up HTML block parsing rules
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks SpaceHeadings // Be strict about prefix heading rules
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four HardLineBreak // Translate newlines into line breaks
EXTENSION_FOOTNOTES // Pandoc-style footnotes TabSizeEight // Expand tabs to eight spaces instead of four
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block Footnotes // Pandoc-style footnotes
EXTENSION_HEADER_IDS // specify header IDs with {#id} NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
EXTENSION_TITLEBLOCK // Titleblock ala pandoc HeadingIDs // specify heading IDs with {#id}
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text Titleblock // Titleblock ala pandoc
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks AutoHeadingIDs // Create the heading ID from the text
EXTENSION_DEFINITION_LISTS // render definition lists BackslashLineBreak // Translate trailing backslashes into line breaks
DefinitionLists // Render definition lists
commonHtmlFlags = 0 | CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
HTML_USE_XHTML | SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
HTML_USE_SMARTYPANTS |
HTML_SMARTYPANTS_FRACTIONS |
HTML_SMARTYPANTS_LATEX_DASHES
commonExtensions = 0 | CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
EXTENSION_NO_INTRA_EMPHASIS | Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
EXTENSION_TABLES | BackslashLineBreak | DefinitionLists
EXTENSION_FENCED_CODE |
EXTENSION_AUTOLINK |
EXTENSION_STRIKETHROUGH |
EXTENSION_SPACE_HEADERS |
EXTENSION_HEADER_IDS |
EXTENSION_BACKSLASH_LINE_BREAK |
EXTENSION_DEFINITION_LISTS
) )
// These are the possible flag values for the link renderer. // ListType contains bitwise or'ed flags for list and list item objects.
// Only a single one of these values will be used; they are not ORed together. type ListType int
// These are mostly of interest if you are writing a new output format.
const (
LINK_TYPE_NOT_AUTOLINK = iota
LINK_TYPE_NORMAL
LINK_TYPE_EMAIL
)
// These are the possible flag values for the ListItem renderer. // These are the possible flag values for the ListItem renderer.
// Multiple flag values may be ORed together. // Multiple flag values may be ORed together.
// These are mostly of interest if you are writing a new output format. // These are mostly of interest if you are writing a new output format.
const ( const (
LIST_TYPE_ORDERED = 1 << iota ListTypeOrdered ListType = 1 << iota
LIST_TYPE_DEFINITION ListTypeDefinition
LIST_TYPE_TERM ListTypeTerm
LIST_ITEM_CONTAINS_BLOCK
LIST_ITEM_BEGINNING_OF_LIST ListItemContainsBlock
LIST_ITEM_END_OF_LIST ListItemBeginningOfList // TODO: figure out if this is of any use now
ListItemEndOfList
) )
// CellAlignFlags holds a type of alignment in a table cell.
type CellAlignFlags int
// These are the possible flag values for the table cell renderer. // These are the possible flag values for the table cell renderer.
// Only a single one of these values will be used; they are not ORed together. // 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. // These are mostly of interest if you are writing a new output format.
const ( const (
TABLE_ALIGNMENT_LEFT = 1 << iota TableAlignmentLeft CellAlignFlags = 1 << iota
TABLE_ALIGNMENT_RIGHT TableAlignmentRight
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT) TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
) )
// The size of a tab stop. // The size of a tab stop.
const ( const (
TAB_SIZE_DEFAULT = 4 TabSizeDefault = 4
TAB_SIZE_EIGHT = 8 TabSizeDouble = 8
) )
// These are the tags that are recognized as HTML block tags. // blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping. // Any of these can be included in markdown text without special escaping.
var blockTags = map[string]bool{ var blockTags = map[string]struct{}{
"p": true, "blockquote": struct{}{},
"dl": true, "del": struct{}{},
"h1": true, "div": struct{}{},
"h2": true, "dl": struct{}{},
"h3": true, "fieldset": struct{}{},
"h4": true, "form": struct{}{},
"h5": true, "h1": struct{}{},
"h6": true, "h2": struct{}{},
"ol": true, "h3": struct{}{},
"ul": true, "h4": struct{}{},
"del": true, "h5": struct{}{},
"div": true, "h6": struct{}{},
"ins": true, "iframe": struct{}{},
"pre": true, "ins": struct{}{},
"form": true, "math": struct{}{},
"math": true, "noscript": struct{}{},
"table": true, "ol": struct{}{},
"iframe": true, "pre": struct{}{},
"script": true, "p": struct{}{},
"fieldset": true, "script": struct{}{},
"noscript": true, "style": struct{}{},
"blockquote": true, "table": struct{}{},
"ul": struct{}{},
// HTML5 // HTML5
"video": true, "address": struct{}{},
"aside": true, "article": struct{}{},
"canvas": true, "aside": struct{}{},
"figure": true, "canvas": struct{}{},
"footer": true, "figcaption": struct{}{},
"header": true, "figure": struct{}{},
"hgroup": true, "footer": struct{}{},
"output": true, "header": struct{}{},
"article": true, "hgroup": struct{}{},
"section": true, "main": struct{}{},
"progress": true, "nav": struct{}{},
"figcaption": true, "output": struct{}{},
"progress": struct{}{},
"section": struct{}{},
"video": struct{}{},
} }
// Renderer is the rendering interface. // Renderer is the rendering interface. This is mostly of interest if you are
// This is mostly of interest if you are implementing a new rendering format. // implementing a new rendering format.
// //
// When a byte slice is provided, it contains the (rendered) contents of the // Only an HTML implementation is provided in this repository, see the README
// element. // for external implementations.
//
// 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.
//
// Currently Html and Latex implementations are provided
type Renderer interface { type Renderer interface {
// block-level callbacks // RenderNode is the main rendering method. It will be called once for
BlockCode(out *bytes.Buffer, text []byte, lang string) // every leaf node and twice for every non-leaf node (first with
BlockQuote(out *bytes.Buffer, text []byte) // entering=true, then with entering=false). The method should write its
BlockHtml(out *bytes.Buffer, text []byte) // rendition of the node to the supplied writer w.
Header(out *bytes.Buffer, text func() bool, level int, id string) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
HRule(out *bytes.Buffer)
List(out *bytes.Buffer, text func() bool, flags int)
ListItem(out *bytes.Buffer, text []byte, flags int)
Paragraph(out *bytes.Buffer, text func() bool)
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
TableRow(out *bytes.Buffer, text []byte)
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
TableCell(out *bytes.Buffer, text []byte, flags int)
Footnotes(out *bytes.Buffer, text func() bool)
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
TitleBlock(out *bytes.Buffer, text []byte)
// Span-level callbacks // RenderHeader is a method that allows the renderer to produce some
AutoLink(out *bytes.Buffer, link []byte, kind int) // content preceding the main body of the output document. The header is
CodeSpan(out *bytes.Buffer, text []byte) // understood in the broad sense here. For example, the default HTML
DoubleEmphasis(out *bytes.Buffer, text []byte) // renderer will write not only the HTML document preamble, but also the
Emphasis(out *bytes.Buffer, text []byte) // table of contents if it was requested.
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) //
LineBreak(out *bytes.Buffer) // The method will be passed an entire document tree, in case a particular
Link(out *bytes.Buffer, link []byte, title []byte, content []byte) // implementation needs to inspect it to produce output.
RawHtmlTag(out *bytes.Buffer, tag []byte) //
TripleEmphasis(out *bytes.Buffer, text []byte) // The output should be written to the supplied writer w. If your
StrikeThrough(out *bytes.Buffer, text []byte) // implementation has no header to write, supply an empty implementation.
FootnoteRef(out *bytes.Buffer, ref []byte, id int) RenderHeader(w io.Writer, ast *Node)
// Low-level callbacks // RenderFooter is a symmetric counterpart of RenderHeader.
Entity(out *bytes.Buffer, entity []byte) RenderFooter(w io.Writer, ast *Node)
NormalText(out *bytes.Buffer, text []byte)
// Header and footer
DocumentHeader(out *bytes.Buffer)
DocumentFooter(out *bytes.Buffer)
GetFlags() int
} }
// Callback functions for inline parsing. One such function is defined // Callback functions for inline parsing. One such function is defined
// for each character that triggers a response when parsing inline data. // for each character that triggers a response when parsing inline data.
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
// Parser holds runtime state used by the parser. // Markdown is a type that holds extensions and the runtime state used by
// This is constructed by the Markdown function. // Parse, and the renderer. You can not use it directly, construct it with New.
type parser struct { type Markdown struct {
r Renderer renderer Renderer
refOverride ReferenceOverrideFunc referenceOverride ReferenceOverrideFunc
refs map[string]*reference refs map[string]*reference
inlineCallback [256]inlineParser inlineCallback [256]inlineParser
flags int extensions Extensions
nesting int nesting int
maxNesting int maxNesting int
insideLink bool insideLink bool
// Footnotes need to be ordered as well as available to quickly check for // Footnotes need to be ordered as well as available to quickly check for
// presence. If a ref is also a footnote, it's stored both in refs and here // presence. If a ref is also a footnote, it's stored both in refs and here
// in notes. Slice is nil if footnotes not enabled. // in notes. Slice is nil if footnotes not enabled.
notes []*reference notes []*reference
doc *Node
tip *Node // = doc
oldTip *Node
lastMatchedContainer *Node // = doc
allClosed bool
} }
func (p *parser) getRef(refid string) (ref *reference, found bool) { func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
if p.refOverride != nil { if p.referenceOverride != nil {
r, overridden := p.refOverride(refid) r, overridden := p.referenceOverride(refid)
if overridden { if overridden {
if r == nil { if r == nil {
return nil, false return nil, false
@ -227,7 +202,7 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
return &reference{ return &reference{
link: []byte(r.Link), link: []byte(r.Link),
title: []byte(r.Title), title: []byte(r.Title),
noteId: 0, noteID: 0,
hasBlock: false, hasBlock: false,
text: []byte(r.Text)}, true text: []byte(r.Text)}, true
} }
@ -237,6 +212,36 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
return ref, found return ref, found
} }
func (p *Markdown) finalize(block *Node) {
above := block.Parent
block.open = false
p.tip = above
}
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
return p.addExistingChild(NewNode(node), offset)
}
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
for !p.tip.canContain(node.Type) {
p.finalize(p.tip)
}
p.tip.AppendChild(node)
p.tip = node
return node
}
func (p *Markdown) closeUnmatchedBlocks() {
if !p.allClosed {
for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.Parent
p.finalize(p.oldTip)
p.oldTip = parent
}
p.allClosed = true
}
}
// //
// //
// Public interface // Public interface
@ -261,102 +266,27 @@ type Reference struct {
// See the documentation in Options for more details on use-case. // See the documentation in Options for more details on use-case.
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
// Options represents configurable overrides and callbacks (in addition to the // New constructs a Markdown processor. You can use the same With* functions as
// extension flag set) for configuring a Markdown parse. // for Run() to customize parser's behavior and the renderer.
type Options struct { func New(opts ...Option) *Markdown {
// Extensions is a flag set of bit-wise ORed extension bits. See the var p Markdown
// EXTENSION_* flags defined in this package. for _, opt := range opts {
Extensions int opt(&p)
// ReferenceOverride is an optional function callback that is called every
// time a reference is resolved.
//
// In Markdown, the link reference syntax can be made to resolve a link to
// a reference instead of an inline URL, in one of the following ways:
//
// * [link text][refid]
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
ReferenceOverride ReferenceOverrideFunc
}
// MarkdownBasic is a convenience function for simple rendering.
// It processes markdown input with no extensions enabled.
func MarkdownBasic(input []byte) []byte {
// set up the HTML renderer
htmlFlags := HTML_USE_XHTML
renderer := HtmlRenderer(htmlFlags, "", "")
// set up the parser
return MarkdownOptions(input, renderer, Options{Extensions: 0})
}
// Call Markdown with most useful extensions enabled
// MarkdownCommon is a convenience function for simple rendering.
// It processes markdown input with common extensions enabled, including:
//
// * Smartypants processing with smart fractions and LaTeX dashes
//
// * Intra-word emphasis suppression
//
// * Tables
//
// * Fenced code blocks
//
// * Autolinking
//
// * Strikethrough support
//
// * Strict header parsing
//
// * Custom Header IDs
func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer
renderer := HtmlRenderer(commonHtmlFlags, "", "")
return MarkdownOptions(input, renderer, Options{
Extensions: commonExtensions})
}
// Markdown is the main rendering function.
// It parses and renders a block of markdown-encoded text.
// 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 HtmlRenderer and
// LatexRenderer, respectively.
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
return MarkdownOptions(input, renderer, Options{
Extensions: extensions})
}
// MarkdownOptions is just like Markdown but takes additional options through
// the Options struct.
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
// no point in parsing if we can't render
if renderer == nil {
return nil
} }
extensions := opts.Extensions
// fill in the render structure
p := new(parser)
p.r = renderer
p.flags = extensions
p.refOverride = opts.ReferenceOverride
p.refs = make(map[string]*reference) p.refs = make(map[string]*reference)
p.maxNesting = 16 p.maxNesting = 16
p.insideLink = false p.insideLink = false
docNode := NewNode(Document)
p.doc = docNode
p.tip = docNode
p.oldTip = docNode
p.lastMatchedContainer = docNode
p.allClosed = true
// register inline parsers // register inline parsers
p.inlineCallback[' '] = maybeLineBreak
p.inlineCallback['*'] = emphasis p.inlineCallback['*'] = emphasis
p.inlineCallback['_'] = emphasis p.inlineCallback['_'] = emphasis
if extensions&EXTENSION_STRIKETHROUGH != 0 { if p.extensions&Strikethrough != 0 {
p.inlineCallback['~'] = emphasis p.inlineCallback['~'] = emphasis
} }
p.inlineCallback['`'] = codeSpan p.inlineCallback['`'] = codeSpan
@ -365,119 +295,166 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
p.inlineCallback['<'] = leftAngle p.inlineCallback['<'] = leftAngle
p.inlineCallback['\\'] = escape p.inlineCallback['\\'] = escape
p.inlineCallback['&'] = entity p.inlineCallback['&'] = entity
p.inlineCallback['!'] = maybeImage
if extensions&EXTENSION_AUTOLINK != 0 { p.inlineCallback['^'] = maybeInlineFootnote
p.inlineCallback[':'] = autoLink if p.extensions&Autolink != 0 {
p.inlineCallback['h'] = maybeAutoLink
p.inlineCallback['m'] = maybeAutoLink
p.inlineCallback['f'] = maybeAutoLink
p.inlineCallback['H'] = maybeAutoLink
p.inlineCallback['M'] = maybeAutoLink
p.inlineCallback['F'] = maybeAutoLink
} }
if p.extensions&Footnotes != 0 {
if extensions&EXTENSION_FOOTNOTES != 0 {
p.notes = make([]*reference, 0) p.notes = make([]*reference, 0)
} }
return &p
first := firstPass(p, input)
second := secondPass(p, first)
return second
} }
// first pass: // Option customizes the Markdown processor's default behavior.
// - extract references type Option func(*Markdown)
// - expand tabs
// - normalize newlines // WithRenderer allows you to override the default renderer.
// - copy everything else func WithRenderer(r Renderer) Option {
// - add missing newlines before fenced code blocks return func(p *Markdown) {
func firstPass(p *parser, input []byte) []byte { p.renderer = r
var out bytes.Buffer
tabSize := TAB_SIZE_DEFAULT
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
tabSize = TAB_SIZE_EIGHT
} }
beg, end := 0, 0
lastLineWasBlank := false
lastFencedCodeBlockEnd := 0
for beg < len(input) { // iterate over lines
if end = isReference(p, input[beg:], tabSize); end > 0 {
beg += end
} else { // skip to the next line
end = beg
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
end++
}
if p.flags&EXTENSION_FENCED_CODE != 0 {
// when last line was none blank and a fenced code block comes after
if beg >= lastFencedCodeBlockEnd {
if i := p.fencedCode(&out, input[beg:], false); i > 0 {
if !lastLineWasBlank {
out.WriteByte('\n') // need to inject additional linebreak
}
lastFencedCodeBlockEnd = beg + i
}
}
lastLineWasBlank = end == beg
}
// add the line body if present
if end > beg {
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
out.Write(input[beg:end])
} else {
expandTabs(&out, input[beg:end], tabSize)
}
}
out.WriteByte('\n')
if end < len(input) && input[end] == '\r' {
end++
}
if end < len(input) && input[end] == '\n' {
end++
}
beg = end
}
}
// empty input?
if out.Len() == 0 {
out.WriteByte('\n')
}
return out.Bytes()
} }
// second pass: actual rendering // WithExtensions allows you to pick some of the many extensions provided by
func secondPass(p *parser, input []byte) []byte { // Blackfriday. You can bitwise OR them.
var output bytes.Buffer func WithExtensions(e Extensions) Option {
return func(p *Markdown) {
p.extensions = e
}
}
p.r.DocumentHeader(&output) // WithNoExtensions turns off all extensions and custom behavior.
p.block(&output, input) func WithNoExtensions() Option {
return func(p *Markdown) {
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 { p.extensions = NoExtensions
p.r.Footnotes(&output, func() bool { p.renderer = NewHTMLRenderer(HTMLRendererParameters{
flags := LIST_ITEM_BEGINNING_OF_LIST Flags: HTMLFlagsNone,
for _, ref := range p.notes {
var buf bytes.Buffer
if ref.hasBlock {
flags |= LIST_ITEM_CONTAINS_BLOCK
p.block(&buf, ref.title)
} else {
p.inline(&buf, ref.title)
}
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
}
return true
}) })
} }
}
p.r.DocumentFooter(&output) // WithRefOverride sets an optional function callback that is called every
// time a reference is resolved.
if p.nesting != 0 { //
panic("Nesting level did not end at zero") // In Markdown, the link reference syntax can be made to resolve a link to
// a reference instead of an inline URL, in one of the following ways:
//
// * [link text][refid]
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
func WithRefOverride(o ReferenceOverrideFunc) Option {
return func(p *Markdown) {
p.referenceOverride = o
} }
}
return output.Bytes() // Run is the main entry point to Blackfriday. It parses and renders a
// block of markdown-encoded text.
//
// The simplest invocation of Run takes one argument, input:
// output := Run(input)
// This will parse the input with CommonExtensions enabled and render it with
// the default HTMLRenderer (with CommonHTMLFlags).
//
// Variadic arguments opts can customize the default behavior. Since Markdown
// type does not contain exported fields, you can not use it directly. Instead,
// use the With* functions. For example, this will call the most basic
// functionality, with no extensions:
// output := Run(input, WithNoExtensions())
//
// You can use any number of With* arguments, even contradicting ones. They
// will be applied in order of appearance and the latter will override the
// former:
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
// WithRenderer(yourRenderer))
func Run(input []byte, opts ...Option) []byte {
r := NewHTMLRenderer(HTMLRendererParameters{
Flags: CommonHTMLFlags,
})
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
optList = append(optList, opts...)
parser := New(optList...)
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
// input markdown document and produces a syntax tree for its contents. This
// tree can then be rendered with a default or custom renderer, or
// analyzed/transformed by the caller to whatever non-standard needs they have.
// The return value is the root node of the syntax tree.
func (p *Markdown) Parse(input []byte) *Node {
p.block(input)
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
}
// Walk the tree again and process inline markdown in each block
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
p.inline(node, node.content)
node.content = nil
}
return GoToNext
})
p.parseRefsToAST()
return p.doc
}
func (p *Markdown) parseRefsToAST() {
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
return
}
p.tip = p.doc
block := p.addBlock(List, nil)
block.IsFootnotesList = true
block.ListFlags = ListTypeOrdered
flags := ListItemBeginningOfList
// Note: this loop is intentionally explicit, not range-form. This is
// because the body of the loop will append nested footnotes to p.notes and
// we need to process those late additions. Range form would only walk over
// the fixed initial set.
for i := 0; i < len(p.notes); i++ {
ref := p.notes[i]
p.addExistingChild(ref.footnote, 0)
block := ref.footnote
block.ListFlags = flags | ListTypeOrdered
block.RefLink = ref.link
if ref.hasBlock {
flags |= ListItemContainsBlock
p.block(ref.title)
} else {
p.inline(block, ref.title)
}
flags &^= ListItemBeginningOfList | ListItemContainsBlock
}
above := block.Parent
finalizeList(block)
p.tip = above
block.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading {
p.inline(node, node.content)
node.content = nil
}
return GoToNext
})
} }
// //
@ -509,13 +486,56 @@ func secondPass(p *parser, input []byte) []byte {
// //
// are not yet supported. // are not yet supported.
// References are parsed and stored in this struct. // reference holds all information necessary for a reference-style links or
// footnotes.
//
// Consider this markdown with reference-style links:
//
// [link][ref]
//
// [ref]: /url/ "tooltip title"
//
// It will be ultimately converted to this HTML:
//
// <p><a href=\"/url/\" title=\"title\">link</a></p>
//
// And a reference structure will be populated as follows:
//
// p.refs["ref"] = &reference{
// link: "/url/",
// title: "tooltip title",
// }
//
// Alternatively, reference can contain information about a footnote. Consider
// this markdown:
//
// Text needing a footnote.[^a]
//
// [^a]: This is the note
//
// A reference structure will be populated as follows:
//
// p.refs["a"] = &reference{
// link: "a",
// title: "This is the note",
// noteID: <some positive int>,
// }
//
// TODO: As you can see, it begs for splitting into two dedicated structures
// for refs and for footnotes.
type reference struct { type reference struct {
link []byte link []byte
title []byte title []byte
noteId int // 0 if not a footnote ref noteID int // 0 if not a footnote ref
hasBlock bool hasBlock bool
text []byte footnote *Node // a link to the Item node within a list of footnotes
text []byte // only gets populated by refOverride feature with Reference.Text
}
func (r *reference) String() string {
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
r.link, r.title, r.text, r.noteID, r.hasBlock)
} }
// Check whether or not data starts with a reference link. // Check whether or not data starts with a reference link.
@ -523,7 +543,7 @@ type reference struct {
// (in the render struct). // (in the render struct).
// Returns the number of bytes to skip to move past it, // Returns the number of bytes to skip to move past it,
// or zero if the first line is not a reference. // or zero if the first line is not a reference.
func isReference(p *parser, data []byte, tabSize int) int { func isReference(p *Markdown, data []byte, tabSize int) int {
// up to 3 optional leading spaces // up to 3 optional leading spaces
if len(data) < 4 { if len(data) < 4 {
return 0 return 0
@ -533,18 +553,18 @@ func isReference(p *parser, data []byte, tabSize int) int {
i++ i++
} }
noteId := 0 noteID := 0
// id part: anything but a newline between brackets // id part: anything but a newline between brackets
if data[i] != '[' { if data[i] != '[' {
return 0 return 0
} }
i++ i++
if p.flags&EXTENSION_FOOTNOTES != 0 { if p.extensions&Footnotes != 0 {
if i < len(data) && data[i] == '^' { if i < len(data) && data[i] == '^' {
// we can set it to anything here because the proper noteIds will // we can set it to anything here because the proper noteIds will
// be assigned later during the second pass. It just has to be != 0 // be assigned later during the second pass. It just has to be != 0
noteId = 1 noteID = 1
i++ i++
} }
} }
@ -556,7 +576,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
return 0 return 0
} }
idEnd := i idEnd := i
// footnotes can have empty ID, like this: [^], but a reference can not be
// empty like this: []. Break early if it's not a footnote and there's no ID
if noteID == 0 && idOffset == idEnd {
return 0
}
// spacer: colon (space | tab)* newline? (space | tab)* // spacer: colon (space | tab)* newline? (space | tab)*
i++ i++
if i >= len(data) || data[i] != ':' { if i >= len(data) || data[i] != ':' {
@ -587,7 +611,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
hasBlock bool hasBlock bool
) )
if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 { if p.extensions&Footnotes != 0 && noteID != 0 {
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
lineEnd = linkEnd lineEnd = linkEnd
} else { } else {
@ -600,11 +624,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
// a valid ref has been found // a valid ref has been found
ref := &reference{ ref := &reference{
noteId: noteId, noteID: noteID,
hasBlock: hasBlock, hasBlock: hasBlock,
} }
if noteId > 0 { if noteID > 0 {
// reusing the link field for the id since footnotes don't have links // reusing the link field for the id since footnotes don't have links
ref.link = data[idOffset:idEnd] ref.link = data[idOffset:idEnd]
// if footnote, it's not really a title, it's the contained text // if footnote, it's not really a title, it's the contained text
@ -622,7 +646,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
return lineEnd return lineEnd
} }
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
// link: whitespace-free sequence, optionally between angle brackets // link: whitespace-free sequence, optionally between angle brackets
if data[i] == '<' { if data[i] == '<' {
i++ i++
@ -631,9 +655,6 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
i++ i++
} }
if i == len(data) {
return
}
linkEnd = i linkEnd = i
if data[linkOffset] == '<' && data[linkEnd-1] == '>' { if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
linkOffset++ linkOffset++
@ -693,13 +714,13 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
return return
} }
// The first bit of this logic is the same as (*parser).listItem, but the rest // The first bit of this logic is the same as Parser.listItem, but the rest
// is much simpler. This function simply finds the entire block and shifts it // is much simpler. This function simply finds the entire block and shifts it
// over by one tab if it is indeed a block (just returns the line if it's not). // over by one tab if it is indeed a block (just returns the line if it's not).
// blockEnd is the end of the section in the input buffer, and contents is the // blockEnd is the end of the section in the input buffer, and contents is the
// extracted text that was shifted over one tab. It will need to be rendered at // extracted text that was shifted over one tab. It will need to be rendered at
// the end of the document. // the end of the document.
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
if i == 0 || len(data) == 0 { if i == 0 || len(data) == 0 {
return return
} }

View file

@ -0,0 +1,38 @@
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
// Unit tests for full document parsing and rendering
//
package blackfriday
import "testing"
func TestDocument(t *testing.T) {
var tests = []string{
// Empty document.
"",
"",
" ",
"",
// This shouldn't panic.
// https://github.com/russross/blackfriday/issues/172
"[]:<",
"<p>[]:&lt;</p>\n",
// This shouldn't panic.
// https://github.com/russross/blackfriday/issues/173
" [",
"<p>[</p>\n",
}
doTests(t, tests)
}

354
vendor/github.com/documize/blackfriday/node.go generated vendored Normal file
View file

@ -0,0 +1,354 @@
package blackfriday
import (
"bytes"
"fmt"
)
// NodeType specifies a type of a single node of a syntax tree. Usually one
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
// or code block.
type NodeType int
// Constants for identifying different types of nodes. See NodeType.
const (
Document NodeType = iota
BlockQuote
List
Item
Paragraph
Heading
HorizontalRule
Emph
Strong
Del
Link
Image
Text
HTMLBlock
CodeBlock
Softbreak
Hardbreak
Code
HTMLSpan
Table
TableCell
TableHead
TableBody
TableRow
)
var nodeTypeNames = []string{
Document: "Document",
BlockQuote: "BlockQuote",
List: "List",
Item: "Item",
Paragraph: "Paragraph",
Heading: "Heading",
HorizontalRule: "HorizontalRule",
Emph: "Emph",
Strong: "Strong",
Del: "Del",
Link: "Link",
Image: "Image",
Text: "Text",
HTMLBlock: "HTMLBlock",
CodeBlock: "CodeBlock",
Softbreak: "Softbreak",
Hardbreak: "Hardbreak",
Code: "Code",
HTMLSpan: "HTMLSpan",
Table: "Table",
TableCell: "TableCell",
TableHead: "TableHead",
TableBody: "TableBody",
TableRow: "TableRow",
}
func (t NodeType) String() string {
return nodeTypeNames[t]
}
// ListData contains fields relevant to a List and Item node type.
type ListData struct {
ListFlags ListType
Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists
Delimiter byte // '.' or ')' after the number in ordered lists
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
IsFootnotesList bool // This is a list of footnotes
}
// LinkData contains fields relevant to a Link node type.
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.
type CodeBlockData struct {
IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string
FenceChar byte
FenceLength int
FenceOffset int
}
// TableCellData contains fields relevant to a TableCell node type.
type TableCellData struct {
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
}
// HeadingData contains fields relevant to a Heading node type.
type HeadingData struct {
Level int // This holds the heading level number
HeadingID string // This might hold heading ID, if present
IsTitleblock bool // Specifies whether it's a title block
}
// Node is a single element in the abstract syntax tree of the parsed document.
// It holds connections to the structurally neighboring nodes and, for certain
// types of nodes, additional information that might be needed when rendering.
type Node struct {
Type NodeType // Determines the type of the node
Parent *Node // Points to the parent
FirstChild *Node // Points to the first child, if any
LastChild *Node // Points to the last child, if any
Prev *Node // Previous sibling; nil if it's the first child
Next *Node // Next sibling; nil if it's the last child
Literal []byte // Text contents of the leaf nodes
HeadingData // Populated if Type is Heading
ListData // Populated if Type is List
CodeBlockData // Populated if Type is CodeBlock
LinkData // Populated if Type is Link
TableCellData // Populated if Type is TableCell
content []byte // Markdown content of the block nodes
open bool // Specifies an open block node that has not been finished to process yet
}
// NewNode allocates a node of a specified type.
func NewNode(typ NodeType) *Node {
return &Node{
Type: typ,
open: true,
}
}
func (n *Node) String() string {
ellipsis := ""
snippet := n.Literal
if len(snippet) > 16 {
snippet = snippet[:16]
ellipsis = "..."
}
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
}
// Unlink removes node 'n' from the tree.
// It panics if the node is nil.
func (n *Node) Unlink() {
if n.Prev != nil {
n.Prev.Next = n.Next
} else if n.Parent != nil {
n.Parent.FirstChild = n.Next
}
if n.Next != nil {
n.Next.Prev = n.Prev
} else if n.Parent != nil {
n.Parent.LastChild = n.Prev
}
n.Parent = nil
n.Next = nil
n.Prev = nil
}
// AppendChild adds a node 'child' as a child of 'n'.
// It panics if either node is nil.
func (n *Node) AppendChild(child *Node) {
child.Unlink()
child.Parent = n
if n.LastChild != nil {
n.LastChild.Next = child
child.Prev = n.LastChild
n.LastChild = child
} else {
n.FirstChild = child
n.LastChild = child
}
}
// InsertBefore inserts 'sibling' immediately before 'n'.
// It panics if either node is nil.
func (n *Node) InsertBefore(sibling *Node) {
sibling.Unlink()
sibling.Prev = n.Prev
if sibling.Prev != nil {
sibling.Prev.Next = sibling
}
sibling.Next = n
n.Prev = sibling
sibling.Parent = n.Parent
if sibling.Prev == nil {
sibling.Parent.FirstChild = sibling
}
}
func (n *Node) isContainer() bool {
switch n.Type {
case Document:
fallthrough
case BlockQuote:
fallthrough
case List:
fallthrough
case Item:
fallthrough
case Paragraph:
fallthrough
case Heading:
fallthrough
case Emph:
fallthrough
case Strong:
fallthrough
case Del:
fallthrough
case Link:
fallthrough
case Image:
fallthrough
case Table:
fallthrough
case TableHead:
fallthrough
case TableBody:
fallthrough
case TableRow:
fallthrough
case TableCell:
return true
default:
return false
}
}
func (n *Node) canContain(t NodeType) bool {
if n.Type == List {
return t == Item
}
if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
return t != Item
}
if n.Type == Table {
return t == TableHead || t == TableBody
}
if n.Type == TableHead || n.Type == TableBody {
return t == TableRow
}
if n.Type == TableRow {
return t == TableCell
}
return false
}
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
// It is returned from NodeVisitor and different values allow Node.Walk to
// decide which node to go to next.
type WalkStatus int
const (
// GoToNext is the default traversal of every node.
GoToNext WalkStatus = iota
// SkipChildren tells walker to skip all children of current node.
SkipChildren
// Terminate tells walker to terminate the traversal.
Terminate
)
// NodeVisitor is a callback to be called when traversing the syntax tree.
// Called twice for every node: once with entering=true when the branch is
// first visited, then with entering=false after all the children are done.
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 (n *Node) Walk(visitor NodeVisitor) {
w := newNodeWalker(n)
for w.current != nil {
status := visitor(w.current, w.entering)
switch status {
case GoToNext:
w.next()
case SkipChildren:
w.entering = false
w.next()
case Terminate:
return
}
}
}
type nodeWalker struct {
current *Node
root *Node
entering bool
}
func newNodeWalker(root *Node) *nodeWalker {
return &nodeWalker{
current: root,
root: root,
entering: true,
}
}
func (nw *nodeWalker) next() {
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
nw.current = nil
return
}
if nw.entering && nw.current.isContainer() {
if nw.current.FirstChild != nil {
nw.current = nw.current.FirstChild
nw.entering = true
} else {
nw.entering = false
}
} else if nw.current.Next == nil {
nw.current = nw.current.Parent
nw.entering = false
} else {
nw.current = nw.current.Next
nw.entering = true
}
}
func dump(ast *Node) {
fmt.Println(dumpString(ast))
}
func dumpR(ast *Node, depth int) string {
if ast == nil {
return ""
}
indent := bytes.Repeat([]byte("\t"), depth)
content := ast.Literal
if content == nil {
content = ast.content
}
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
for n := ast.FirstChild; n != nil; n = n.Next {
result += dumpR(n, depth+1)
}
return result
}
func dumpString(ast *Node) string {
return dumpR(ast, 0)
}

View file

@ -19,58 +19,6 @@ import (
"testing" "testing"
) )
func runMarkdownReference(input string, flag int) string {
renderer := HtmlRenderer(0, "", "")
return string(Markdown([]byte(input), renderer, flag))
}
func doTestsReference(t *testing.T, files []string, flag int) {
// catch and report panics
var candidate string
defer func() {
if err := recover(); err != nil {
t.Errorf("\npanic while processing [%#v]\n", candidate)
}
}()
for _, basename := range files {
filename := filepath.Join("testdata", basename+".text")
inputBytes, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
continue
}
input := string(inputBytes)
filename = filepath.Join("testdata", basename+".html")
expectedBytes, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
continue
}
expected := string(expectedBytes)
// fmt.Fprintf(os.Stderr, "processing %s ...", filename)
actual := string(runMarkdownReference(input, flag))
if actual != expected {
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
basename+".text", expected, actual)
}
// fmt.Fprintf(os.Stderr, " ok\n")
// now test every prefix of every input to check for
// bounds checking
if !testing.Short() {
start, max := 0, len(input)
for end := start + 1; end <= max; end++ {
candidate = input[start:end]
// fmt.Fprintf(os.Stderr, " %s %d:%d/%d\n", filename, start, end, max)
_ = runMarkdownReference(candidate, flag)
}
}
}
}
func TestReference(t *testing.T) { func TestReference(t *testing.T) {
files := []string{ files := []string{
"Amps and angle encoding", "Amps and angle encoding",
@ -124,5 +72,53 @@ func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
"Tabs", "Tabs",
"Tidyness", "Tidyness",
} }
doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) doTestsReference(t, files, NoEmptyLineBeforeBlock)
}
// benchResultAnchor is an anchor variable to store the result of a benchmarked
// code so that compiler could never optimize away the call to runMarkdown()
var benchResultAnchor string
func BenchmarkReference(b *testing.B) {
params := TestParams{extensions: CommonExtensions}
files := []string{
"Amps and angle encoding",
"Auto links",
"Backslash escapes",
"Blockquotes with code blocks",
"Code Blocks",
"Code Spans",
"Hard-wrapped paragraphs with list-like lines",
"Horizontal rules",
"Inline HTML (Advanced)",
"Inline HTML (Simple)",
"Inline HTML comments",
"Links, inline style",
"Links, reference style",
"Links, shortcut references",
"Literal quotes in titles",
"Markdown Documentation - Basics",
"Markdown Documentation - Syntax",
"Nested blockquotes",
"Ordered and unordered lists",
"Strong and em together",
"Tabs",
"Tidyness",
}
var tests []string
for _, basename := range files {
filename := filepath.Join("testdata", basename+".text")
inputBytes, err := ioutil.ReadFile(filename)
if err != nil {
b.Errorf("Couldn't open '%s', error: %v\n", filename, err)
continue
}
tests = append(tests, string(inputBytes))
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, test := range tests {
benchResultAnchor = runMarkdown(test, params)
}
}
} }

View file

@ -17,11 +17,14 @@ package blackfriday
import ( import (
"bytes" "bytes"
"io"
) )
type smartypantsData struct { // SPRenderer is a struct containing state of a Smartypants renderer.
type SPRenderer struct {
inSingleQuote bool inSingleQuote bool
inDoubleQuote bool inDoubleQuote bool
callbacks [256]smartCallback
} }
func wordBoundary(c byte) bool { func wordBoundary(c byte) bool {
@ -39,7 +42,7 @@ func isdigit(c byte) bool {
return c >= '0' && c <= '9' return c >= '0' && c <= '9'
} }
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool { func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
// edge of the buffer is likely to be a tag that we don't get to see, // edge of the buffer is likely to be a tag that we don't get to see,
// so we treat it like text sometimes // so we treat it like text sometimes
@ -96,6 +99,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
*isOpen = false *isOpen = false
} }
// Note that with the limited lookahead, this non-breaking
// space will also be appended to single double quotes.
if addNBSP && !*isOpen {
out.WriteString("&nbsp;")
}
out.WriteByte('&') out.WriteByte('&')
if *isOpen { if *isOpen {
out.WriteByte('l') out.WriteByte('l')
@ -104,10 +113,15 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
} }
out.WriteByte(quote) out.WriteByte(quote)
out.WriteString("quo;") out.WriteString("quo;")
if addNBSP && *isOpen {
out.WriteString("&nbsp;")
}
return true return true
} }
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
t1 := tolower(text[1]) t1 := tolower(text[1])
@ -116,7 +130,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -141,7 +155,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
return 0 return 0
} }
@ -149,7 +163,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
return 0 return 0
} }
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 { if len(text) >= 3 {
t1 := tolower(text[1]) t1 := tolower(text[1])
t2 := tolower(text[2]) t2 := tolower(text[2])
@ -174,7 +188,7 @@ func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
return 0 return 0
} }
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
if text[1] == '-' { if text[1] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
@ -191,7 +205,7 @@ func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text
return 0 return 0
} }
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '-' && text[2] == '-' { if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
return 2 return 2
@ -205,13 +219,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
return 0 return 0
} }
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
if bytes.HasPrefix(text, []byte("&quot;")) { if bytes.HasPrefix(text, []byte("&quot;")) {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 7 { if len(text) >= 7 {
nextChar = text[6] nextChar = text[6]
} }
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
return 5 return 5
} }
} }
@ -224,15 +238,18 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
return 0 return 0
} }
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, 'd') var quote byte = 'd'
if angledQuotes {
quote = 'a'
}
return func(out *bytes.Buffer, previousChar byte, text []byte) int {
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
}
} }
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, 'a')
}
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '.' && text[2] == '.' { if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
out.WriteString("&hellip;") out.WriteString("&hellip;")
return 2 return 2
@ -247,13 +264,13 @@ func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
return 0 return 0
} }
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 && text[1] == '`' { if len(text) >= 2 && text[1] == '`' {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -262,7 +279,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
return 0 return 0
} }
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
// note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8) // note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8)
@ -304,7 +321,7 @@ func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar b
return 0 return 0
} }
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
if text[0] == '1' && text[1] == '/' && text[2] == '2' { if text[0] == '1' && text[1] == '/' && text[2] == '2' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
@ -332,27 +349,27 @@ func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
return 0 return 0
} }
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
nextChar := byte(0) nextChar := byte(0)
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
out.WriteString("&quot;") out.WriteString("&quot;")
} }
return 0 return 0
} }
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd') return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
} }
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a') return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
} }
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
i := 0 i := 0
for i < len(text) && text[i] != '>' { for i < len(text) && text[i] != '>' {
@ -363,36 +380,78 @@ func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
return i return i
} }
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
type smartypantsRenderer [256]smartCallback // NewSmartypantsRenderer constructs a Smartypants renderer object.
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
var (
r SPRenderer
func smartypants(flags int) *smartypantsRenderer { smartAmpAngled = r.smartAmp(true, false)
r := new(smartypantsRenderer) smartAmpAngledNBSP = r.smartAmp(true, true)
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 { smartAmpRegular = r.smartAmp(false, false)
r['"'] = smartDoubleQuote smartAmpRegularNBSP = r.smartAmp(false, true)
r['&'] = smartAmp
addNBSP = flags&SmartypantsQuotesNBSP != 0
)
if flags&SmartypantsAngledQuotes == 0 {
r.callbacks['"'] = r.smartDoubleQuote
if !addNBSP {
r.callbacks['&'] = smartAmpRegular
} else {
r.callbacks['&'] = smartAmpRegularNBSP
}
} else { } else {
r['"'] = smartAngledDoubleQuote r.callbacks['"'] = r.smartAngledDoubleQuote
r['&'] = smartAmpAngledQuote if !addNBSP {
} r.callbacks['&'] = smartAmpAngled
r['\''] = smartSingleQuote } else {
r['('] = smartParens r.callbacks['&'] = smartAmpAngledNBSP
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
r['-'] = smartDash
} else {
r['-'] = smartDashLatex
}
r['.'] = smartPeriod
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
r['1'] = smartNumber
r['3'] = smartNumber
} else {
for ch := '1'; ch <= '9'; ch++ {
r[ch] = smartNumberGeneric
} }
} }
r['<'] = smartLeftAngle r.callbacks['\''] = r.smartSingleQuote
r['`'] = smartBacktick r.callbacks['('] = r.smartParens
return r if flags&SmartypantsDashes != 0 {
if flags&SmartypantsLatexDashes == 0 {
r.callbacks['-'] = r.smartDash
} else {
r.callbacks['-'] = r.smartDashLatex
}
}
r.callbacks['.'] = r.smartPeriod
if flags&SmartypantsFractions == 0 {
r.callbacks['1'] = r.smartNumber
r.callbacks['3'] = r.smartNumber
} else {
for ch := '1'; ch <= '9'; ch++ {
r.callbacks[ch] = r.smartNumberGeneric
}
}
r.callbacks['<'] = r.smartLeftAngle
r.callbacks['`'] = r.smartBacktick
return &r
}
// Process is the entry point of the Smartypants renderer.
func (r *SPRenderer) Process(w io.Writer, text []byte) {
mark := 0
for i := 0; i < len(text); i++ {
if action := r.callbacks[text[i]]; action != nil {
if i > mark {
w.Write(text[mark:i])
}
previousChar := byte(0)
if i > 0 {
previousChar = text[i-1]
}
var tmp bytes.Buffer
i += action(&tmp, previousChar, text[i:])
w.Write(tmp.Bytes())
mark = i + 1
}
}
if mark < len(text) {
w.Write(text[mark:])
}
} }

View file

@ -1,13 +1,13 @@
<p>Here's a simple block:</p> <p>Here's a simple block:</p>
<div> <div>
foo foo
</div> </div>
<p>This should be a code block, though:</p> <p>This should be a code block, though:</p>
<pre><code>&lt;div&gt; <pre><code>&lt;div&gt;
foo foo
&lt;/div&gt; &lt;/div&gt;
</code></pre> </code></pre>
@ -19,11 +19,11 @@
<p>Now, nested:</p> <p>Now, nested:</p>
<div> <div>
<div> <div>
<div> <div>
foo foo
</div> </div>
</div> </div>
</div> </div>
<p>This should just be an HTML comment:</p> <p>This should just be an HTML comment:</p>

View file

@ -3,7 +3,7 @@
<!-- This is a simple comment --> <!-- This is a simple comment -->
<!-- <!--
This is another comment. This is another comment.
--> -->
<p>Paragraph two.</p> <p>Paragraph two.</p>

View file

@ -939,8 +939,8 @@ _ underscore
[] square brackets [] square brackets
() parentheses () parentheses
# hash mark # hash mark
+ plus sign + plus sign
- minus sign (hyphen) - minus sign (hyphen)
. dot . dot
! exclamation mark ! exclamation mark
</code></pre> </code></pre>

View file

@ -13,13 +13,13 @@ indented with spaces</p></li>
<p>And:</p> <p>And:</p>
<pre><code> this code block is indented by two tabs <pre><code> this code block is indented by two tabs
</code></pre> </code></pre>
<p>And:</p> <p>And:</p>
<pre><code>+ this is an example list item <pre><code>+ this is an example list item
indented with tabs indented with tabs
+ this is an example list item + this is an example list item
indented with spaces indented with spaces