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:
commit
4bed88b4fd
27
inline.go
27
inline.go
@ -216,10 +216,10 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
data = data[offset:]
|
||||
|
||||
var (
|
||||
i = 1
|
||||
noteId int
|
||||
title, link []byte
|
||||
textHasNl = false
|
||||
i = 1
|
||||
noteId int
|
||||
title, link, altContent []byte
|
||||
textHasNl = false
|
||||
)
|
||||
|
||||
if t == linkDeferredFootnote {
|
||||
@ -355,6 +355,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
// reference style link
|
||||
case i < len(data)-1 && data[i] == '[' && data[i+1] != '^':
|
||||
var id []byte
|
||||
altContentConsidered := false
|
||||
|
||||
// look for the id
|
||||
i++
|
||||
@ -384,14 +385,14 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
id = b.Bytes()
|
||||
} else {
|
||||
id = data[1:txtE]
|
||||
altContentConsidered = true
|
||||
}
|
||||
} else {
|
||||
id = data[linkB:linkE]
|
||||
}
|
||||
|
||||
// find the reference with matching id (ids are case-insensitive)
|
||||
key := string(bytes.ToLower(id))
|
||||
lr, ok := p.refs[key]
|
||||
// find the reference with matching id
|
||||
lr, ok := p.getRef(string(id))
|
||||
if !ok {
|
||||
return 0
|
||||
|
||||
@ -400,6 +401,9 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
// keep link and title from reference
|
||||
link = lr.link
|
||||
title = lr.title
|
||||
if altContentConsidered {
|
||||
altContent = lr.text
|
||||
}
|
||||
i++
|
||||
|
||||
// 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 {
|
||||
// create a new reference
|
||||
noteId = len(p.notes) + 1
|
||||
@ -458,7 +461,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
title = ref.title
|
||||
} else {
|
||||
// find the reference with matching id
|
||||
lr, ok := p.refs[key]
|
||||
lr, ok := p.getRef(string(id))
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
@ -510,7 +513,11 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
// call the relevant rendering function
|
||||
switch t {
|
||||
case linkNormal:
|
||||
p.r.Link(out, uLink, title, content.Bytes())
|
||||
if len(altContent) > 0 {
|
||||
p.r.Link(out, uLink, title, altContent)
|
||||
} else {
|
||||
p.r.Link(out, uLink, title, content.Bytes())
|
||||
}
|
||||
|
||||
case linkImg:
|
||||
outSize := out.Len()
|
||||
|
106
inline_test.go
106
inline_test.go
@ -20,19 +20,19 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func runMarkdownInline(input string, extensions, htmlFlags int, params HtmlRendererParameters) string {
|
||||
extensions |= EXTENSION_AUTOLINK
|
||||
extensions |= EXTENSION_STRIKETHROUGH
|
||||
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(Markdown([]byte(input), renderer, extensions))
|
||||
return string(MarkdownOptions([]byte(input), renderer, opts))
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -41,22 +41,22 @@ func doLinkTestsInline(t *testing.T, tests []string) {
|
||||
prefix := "http://localhost"
|
||||
params := HtmlRendererParameters{AbsolutePrefix: prefix}
|
||||
transformTests := transformLinks(tests, prefix)
|
||||
doTestsInlineParam(t, transformTests, 0, 0, params)
|
||||
doTestsInlineParam(t, transformTests, 0, commonHtmlFlags, params)
|
||||
doTestsInlineParam(t, transformTests, Options{}, 0, params)
|
||||
doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params)
|
||||
}
|
||||
|
||||
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
|
||||
// just rerun it with different parameters and the same expectations.
|
||||
prefix := "http://localhost"
|
||||
params := HtmlRendererParameters{AbsolutePrefix: 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) {
|
||||
// catch and report panics
|
||||
var candidate string
|
||||
@ -72,8 +72,7 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int,
|
||||
input := tests[i]
|
||||
candidate = input
|
||||
expected := tests[i+1]
|
||||
|
||||
actual := runMarkdownInline(candidate, extensions, htmlFlags, params)
|
||||
actual := runMarkdownInline(candidate, opts, htmlFlags, params)
|
||||
if actual != expected {
|
||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||
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 end := start + 1; end <= len(input); 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
var tests = []string{
|
||||
"nothing inline\n",
|
||||
@ -359,7 +415,9 @@ func TestLineBreak(t *testing.T) {
|
||||
"this has an \nextra space\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) {
|
||||
@ -499,7 +557,7 @@ func TestRelAttrLink(t *testing.T) {
|
||||
"[foo](../bar)\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{})
|
||||
|
||||
var noreferrerTests = []string{
|
||||
@ -509,7 +567,7 @@ func TestRelAttrLink(t *testing.T) {
|
||||
"[foo](/bar/)\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{})
|
||||
|
||||
var nofollownoreferrerTests = []string{
|
||||
@ -519,7 +577,7 @@ func TestRelAttrLink(t *testing.T) {
|
||||
"[foo](/bar/)\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{})
|
||||
}
|
||||
|
||||
@ -547,7 +605,7 @@ func TestHrefTargetBlank(t *testing.T) {
|
||||
"[foo](http://example.com)\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) {
|
||||
@ -887,7 +945,7 @@ what happens here
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -912,7 +970,7 @@ func TestFootnotesWithParameters(t *testing.T) {
|
||||
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) {
|
||||
@ -924,7 +982,7 @@ func TestSmartDoubleQuotes(t *testing.T) {
|
||||
"two pair of \"some\" quoted \"text\".\n",
|
||||
"<p>two pair of “some” quoted “text”.</p>\n"}
|
||||
|
||||
doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||
@ -936,7 +994,7 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||
"two pair of \"some\" quoted \"text\".\n",
|
||||
"<p>two pair of «some» quoted «text».</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) {
|
||||
@ -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",
|
||||
"<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{
|
||||
"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",
|
||||
"<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{})
|
||||
}
|
||||
|
82
markdown.go
82
markdown.go
@ -20,6 +20,7 @@ package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"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.
|
||||
type parser struct {
|
||||
r Renderer
|
||||
refOverride ReferenceOverrideFunc
|
||||
refs map[string]*reference
|
||||
inlineCallback [256]inlineParser
|
||||
flags int
|
||||
@ -211,12 +213,74 @@ type parser struct {
|
||||
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
|
||||
//
|
||||
//
|
||||
|
||||
// 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.
|
||||
// It processes markdown input with no extensions enabled.
|
||||
func MarkdownBasic(input []byte) []byte {
|
||||
@ -225,9 +289,7 @@ func MarkdownBasic(input []byte) []byte {
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
// set up the parser
|
||||
extensions := 0
|
||||
|
||||
return Markdown(input, renderer, extensions)
|
||||
return MarkdownOptions(input, renderer, Options{Extensions: 0})
|
||||
}
|
||||
|
||||
// Call Markdown with most useful extensions enabled
|
||||
@ -252,7 +314,8 @@ func MarkdownBasic(input []byte) []byte {
|
||||
func MarkdownCommon(input []byte) []byte {
|
||||
// set up the HTML renderer
|
||||
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
||||
return Markdown(input, renderer, commonExtensions)
|
||||
return MarkdownOptions(input, renderer, Options{
|
||||
Extensions: commonExtensions})
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.maxNesting = 16
|
||||
p.insideLink = false
|
||||
@ -438,6 +511,7 @@ type reference struct {
|
||||
title []byte
|
||||
noteId int // 0 if not a footnote ref
|
||||
hasBlock bool
|
||||
text []byte
|
||||
}
|
||||
|
||||
// Check whether or not data starts with a reference link.
|
||||
|
Loading…
Reference in New Issue
Block a user