1
0
mirror of https://github.com/danog/blackfriday.git synced 2024-11-27 04:24:41 +01:00

Merge branch 'jtolds-master'

This commit is contained in:
Vytautas Šaltenis 2015-05-06 16:00:36 +03:00
commit 4bed88b4fd
3 changed files with 177 additions and 38 deletions

View File

@ -218,7 +218,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
var ( var (
i = 1 i = 1
noteId int noteId int
title, link []byte title, link, altContent []byte
textHasNl = false textHasNl = false
) )
@ -355,6 +355,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
// reference style link // reference style link
case i < len(data)-1 && data[i] == '[' && data[i+1] != '^': case i < len(data)-1 && data[i] == '[' && data[i+1] != '^':
var id []byte var id []byte
altContentConsidered := false
// look for the id // look for the id
i++ i++
@ -384,14 +385,14 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
id = b.Bytes() id = b.Bytes()
} else { } else {
id = data[1:txtE] id = data[1:txtE]
altContentConsidered = true
} }
} else { } else {
id = data[linkB:linkE] id = data[linkB:linkE]
} }
// find the reference with matching id (ids are case-insensitive) // find the reference with matching id
key := string(bytes.ToLower(id)) lr, ok := p.getRef(string(id))
lr, ok := p.refs[key]
if !ok { if !ok {
return 0 return 0
@ -400,6 +401,9 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
// keep link and title from reference // keep link and title from reference
link = lr.link link = lr.link
title = lr.title title = lr.title
if altContentConsidered {
altContent = lr.text
}
i++ i++
// shortcut reference style link or reference or inline footnote // shortcut reference style link or reference or inline footnote
@ -428,7 +432,6 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
} }
} }
key := string(bytes.ToLower(id))
if t == linkInlineFootnote { if t == linkInlineFootnote {
// create a new reference // create a new reference
noteId = len(p.notes) + 1 noteId = len(p.notes) + 1
@ -458,7 +461,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
title = ref.title title = ref.title
} else { } else {
// find the reference with matching id // find the reference with matching id
lr, ok := p.refs[key] lr, ok := p.getRef(string(id))
if !ok { if !ok {
return 0 return 0
} }
@ -510,7 +513,11 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
// call the relevant rendering function // call the relevant rendering function
switch t { switch t {
case linkNormal: case linkNormal:
if len(altContent) > 0 {
p.r.Link(out, uLink, title, altContent)
} else {
p.r.Link(out, uLink, title, content.Bytes()) p.r.Link(out, uLink, title, content.Bytes())
}
case linkImg: case linkImg:
outSize := out.Len() outSize := out.Len()

View File

@ -20,19 +20,19 @@ import (
"strings" "strings"
) )
func runMarkdownInline(input string, extensions, htmlFlags int, params HtmlRendererParameters) string { func runMarkdownInline(input string, opts Options, htmlFlags int, params HtmlRendererParameters) string {
extensions |= EXTENSION_AUTOLINK opts.Extensions |= EXTENSION_AUTOLINK
extensions |= EXTENSION_STRIKETHROUGH opts.Extensions |= EXTENSION_STRIKETHROUGH
htmlFlags |= HTML_USE_XHTML htmlFlags |= HTML_USE_XHTML
renderer := HtmlRendererWithParameters(htmlFlags, "", "", params) renderer := HtmlRendererWithParameters(htmlFlags, "", "", params)
return string(Markdown([]byte(input), renderer, extensions)) return string(MarkdownOptions([]byte(input), renderer, opts))
} }
func doTestsInline(t *testing.T, tests []string) { func doTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, 0, 0, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, 0, HtmlRendererParameters{})
} }
func doLinkTestsInline(t *testing.T, tests []string) { func doLinkTestsInline(t *testing.T, tests []string) {
@ -41,22 +41,22 @@ func doLinkTestsInline(t *testing.T, tests []string) {
prefix := "http://localhost" prefix := "http://localhost"
params := HtmlRendererParameters{AbsolutePrefix: prefix} params := HtmlRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix) transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, 0, 0, params) doTestsInlineParam(t, transformTests, Options{}, 0, params)
doTestsInlineParam(t, transformTests, 0, commonHtmlFlags, params) doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params)
} }
func doSafeTestsInline(t *testing.T, tests []string) { func doSafeTestsInline(t *testing.T, tests []string) {
doTestsInlineParam(t, tests, 0, HTML_SAFELINK, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK, HtmlRendererParameters{})
// All the links in this test should not have the prefix appended, so // All the links in this test should not have the prefix appended, so
// just rerun it with different parameters and the same expectations. // just rerun it with different parameters and the same expectations.
prefix := "http://localhost" prefix := "http://localhost"
params := HtmlRendererParameters{AbsolutePrefix: prefix} params := HtmlRendererParameters{AbsolutePrefix: prefix}
transformTests := transformLinks(tests, prefix) transformTests := transformLinks(tests, prefix)
doTestsInlineParam(t, transformTests, 0, HTML_SAFELINK, params) doTestsInlineParam(t, transformTests, Options{}, HTML_SAFELINK, params)
} }
func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int, func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags int,
params HtmlRendererParameters) { params HtmlRendererParameters) {
// catch and report panics // catch and report panics
var candidate string var candidate string
@ -72,8 +72,7 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int,
input := tests[i] input := tests[i]
candidate = input candidate = input
expected := tests[i+1] expected := tests[i+1]
actual := runMarkdownInline(candidate, opts, htmlFlags, params)
actual := runMarkdownInline(candidate, extensions, htmlFlags, params)
if actual != expected { if actual != expected {
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
candidate, expected, actual) candidate, expected, actual)
@ -84,7 +83,7 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int,
for start := 0; start < len(input); start++ { for start := 0; start < len(input); start++ {
for end := start + 1; end <= len(input); end++ { for end := start + 1; end <= len(input); end++ {
candidate = input[start:end] candidate = input[start:end]
_ = runMarkdownInline(candidate, extensions, htmlFlags, params) _ = runMarkdownInline(candidate, opts, htmlFlags, params)
} }
} }
} }
@ -158,6 +157,63 @@ func TestEmphasis(t *testing.T) {
doTestsInline(t, tests) doTestsInline(t, tests)
} }
func TestReferenceOverride(t *testing.T) {
var tests = []string{
"test [ref1][]\n",
"<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">ref1</a></p>\n",
"test [my ref][ref1]\n",
"<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">my ref</a></p>\n",
"test [ref2][]\n\n[ref2]: http://www.leftalone.com/ (Ref left alone)\n",
"<p>test <a href=\"http://www.overridden.com/\" title=\"Reference Overridden\">ref2</a></p>\n",
"test [ref3][]\n\n[ref3]: http://www.leftalone.com/ (Ref left alone)\n",
"<p>test <a href=\"http://www.leftalone.com/\" title=\"Ref left alone\">ref3</a></p>\n",
"test [ref4][]\n\n[ref4]: http://zombo.com/ (You can do anything)\n",
"<p>test [ref4][]</p>\n",
"test [!(*http.ServeMux).ServeHTTP][] complicated ref\n",
"<p>test <a href=\"http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP\" title=\"ServeHTTP docs\">!(*http.ServeMux).ServeHTTP</a> complicated ref</p>\n",
"test [ref5][]\n",
"<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n",
}
doTestsInlineParam(t, tests, Options{
ReferenceOverride: func(reference string) (rv *Reference, overridden bool) {
switch reference {
case "ref1":
// just an overriden reference exists without definition
return &Reference{
Link: "http://www.ref1.com/",
Title: "Reference 1"}, true
case "ref2":
// overridden exists and reference defined
return &Reference{
Link: "http://www.overridden.com/",
Title: "Reference Overridden"}, true
case "ref3":
// not overridden and reference defined
return nil, false
case "ref4":
// overridden missing and defined
return nil, true
case "!(*http.ServeMux).ServeHTTP":
return &Reference{
Link: "http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP",
Title: "ServeHTTP docs"}, true
case "ref5":
return &Reference{
Link: "http://www.ref5.com/",
Title: "Reference 5",
Text: "Moo",
}, true
}
return nil, false
}}, 0, HtmlRendererParameters{})
}
func TestStrong(t *testing.T) { func TestStrong(t *testing.T) {
var tests = []string{ var tests = []string{
"nothing inline\n", "nothing inline\n",
@ -359,7 +415,9 @@ 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, EXTENSION_BACKSLASH_LINE_BREAK, 0, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{
Extensions: EXTENSION_BACKSLASH_LINE_BREAK},
0, HtmlRendererParameters{})
} }
func TestInlineLink(t *testing.T) { func TestInlineLink(t *testing.T) {
@ -499,7 +557,7 @@ 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, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, doTestsInlineParam(t, nofollowTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS,
HtmlRendererParameters{}) HtmlRendererParameters{})
var noreferrerTests = []string{ var noreferrerTests = []string{
@ -509,7 +567,7 @@ 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, 0, HTML_SAFELINK|HTML_NOREFERRER_LINKS, doTestsInlineParam(t, noreferrerTests, Options{}, HTML_SAFELINK|HTML_NOREFERRER_LINKS,
HtmlRendererParameters{}) HtmlRendererParameters{})
var nofollownoreferrerTests = []string{ var nofollownoreferrerTests = []string{
@ -519,7 +577,7 @@ 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, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS, doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS,
HtmlRendererParameters{}) HtmlRendererParameters{})
} }
@ -547,7 +605,7 @@ 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, 0, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{})
} }
func TestSafeInlineLink(t *testing.T) { func TestSafeInlineLink(t *testing.T) {
@ -887,7 +945,7 @@ what happens here
} }
func TestFootnotes(t *testing.T) { func TestFootnotes(t *testing.T) {
doTestsInlineParam(t, footnoteTests, EXTENSION_FOOTNOTES, 0, HtmlRendererParameters{}) doTestsInlineParam(t, footnoteTests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, HtmlRendererParameters{})
} }
func TestFootnotesWithParameters(t *testing.T) { func TestFootnotesWithParameters(t *testing.T) {
@ -912,7 +970,7 @@ func TestFootnotesWithParameters(t *testing.T) {
FootnoteReturnLinkContents: returnText, FootnoteReturnLinkContents: returnText,
} }
doTestsInlineParam(t, tests, EXTENSION_FOOTNOTES, HTML_FOOTNOTE_RETURN_LINKS, params) doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, HTML_FOOTNOTE_RETURN_LINKS, params)
} }
func TestSmartDoubleQuotes(t *testing.T) { func TestSmartDoubleQuotes(t *testing.T) {
@ -924,7 +982,7 @@ 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, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
} }
func TestSmartAngledDoubleQuotes(t *testing.T) { func TestSmartAngledDoubleQuotes(t *testing.T) {
@ -936,7 +994,7 @@ 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, 0, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
} }
func TestSmartFractions(t *testing.T) { func TestSmartFractions(t *testing.T) {
@ -946,7 +1004,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, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
tests = []string{ tests = []string{
"1/2, 2/3, 81/100 and 1000000/1048576.\n", "1/2, 2/3, 81/100 and 1000000/1048576.\n",
@ -954,5 +1012,5 @@ 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, 0, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{}) doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{})
} }

View File

@ -20,6 +20,7 @@ package blackfriday
import ( import (
"bytes" "bytes"
"strings"
"unicode/utf8" "unicode/utf8"
) )
@ -198,6 +199,7 @@ type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) in
// This is constructed by the Markdown function. // This is constructed by the Markdown function.
type parser struct { type parser struct {
r Renderer r Renderer
refOverride ReferenceOverrideFunc
refs map[string]*reference refs map[string]*reference
inlineCallback [256]inlineParser inlineCallback [256]inlineParser
flags int flags int
@ -211,12 +213,74 @@ type parser struct {
notes []*reference notes []*reference
} }
func (p *parser) getRef(refid string) (ref *reference, found bool) {
if p.refOverride != nil {
r, overridden := p.refOverride(refid)
if overridden {
if r == nil {
return nil, false
}
return &reference{
link: []byte(r.Link),
title: []byte(r.Title),
noteId: 0,
hasBlock: false,
text: []byte(r.Text)}, true
}
}
// refs are case insensitive
ref, found = p.refs[strings.ToLower(refid)]
return ref, found
}
// //
// //
// Public interface // Public interface
// //
// //
// Reference represents the details of a link.
// See the documentation in Options for more details on use-case.
type Reference struct {
// Link is usually the URL the reference points to.
Link string
// Title is the alternate text describing the link in more detail.
Title string
// Text is the optional text to override the ref with if the syntax used was
// [refid][]
Text string
}
// ReferenceOverrideFunc is expected to be called with a reference string and
// return either a valid Reference type that the reference string maps to or
// nil. If overridden is false, the default reference logic will be executed.
// See the documentation in Options for more details on use-case.
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
// Options represents configurable overrides and callbacks (in addition to the
// extension flag set) for configuring a Markdown parse.
type Options struct {
// Extensions is a flag set of bit-wise ORed extension bits. See the
// EXTENSION_* flags defined in this package.
Extensions int
// 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. // MarkdownBasic is a convenience function for simple rendering.
// It processes markdown input with no extensions enabled. // It processes markdown input with no extensions enabled.
func MarkdownBasic(input []byte) []byte { func MarkdownBasic(input []byte) []byte {
@ -225,9 +289,7 @@ func MarkdownBasic(input []byte) []byte {
renderer := HtmlRenderer(htmlFlags, "", "") renderer := HtmlRenderer(htmlFlags, "", "")
// set up the parser // set up the parser
extensions := 0 return MarkdownOptions(input, renderer, Options{Extensions: 0})
return Markdown(input, renderer, extensions)
} }
// Call Markdown with most useful extensions enabled // Call Markdown with most useful extensions enabled
@ -252,7 +314,8 @@ func MarkdownBasic(input []byte) []byte {
func MarkdownCommon(input []byte) []byte { func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer // set up the HTML renderer
renderer := HtmlRenderer(commonHtmlFlags, "", "") renderer := HtmlRenderer(commonHtmlFlags, "", "")
return Markdown(input, renderer, commonExtensions) return MarkdownOptions(input, renderer, Options{
Extensions: commonExtensions})
} }
// Markdown is the main rendering function. // Markdown is the main rendering function.
@ -263,15 +326,25 @@ func MarkdownCommon(input []byte) []byte {
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and // To use the supplied Html or LaTeX renderers, see HtmlRenderer and
// LatexRenderer, respectively. // LatexRenderer, respectively.
func Markdown(input []byte, renderer Renderer, extensions int) []byte { 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 // no point in parsing if we can't render
if renderer == nil { if renderer == nil {
return nil return nil
} }
extensions := opts.Extensions
// fill in the render structure // fill in the render structure
p := new(parser) p := new(parser)
p.r = renderer p.r = renderer
p.flags = extensions 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
@ -438,6 +511,7 @@ type reference struct {
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
} }
// Check whether or not data starts with a reference link. // Check whether or not data starts with a reference link.