all repos — grayfriday @ 3a1d5152426fb4f166bab351213c0249d4fd3f61

blackfriday fork with a few changes

Add Smartypants support for French Guillemets

This is a port of the fix for #378 in v1.

Fixes #380
Bjørn Erik Pedersen bjorn.erik.pedersen@gmail.com
Mon, 31 Jul 2017 17:40:36 +0200
commit

3a1d5152426fb4f166bab351213c0249d4fd3f61

parent

f42ca5bf18fa5a63ca3a33fe9c5d0415f257eb0a

3 files changed, 82 insertions(+), 15 deletions(-)

jump to
M html.gohtml.go

@@ -44,6 +44,7 @@ SmartypantsFractions // Enable smart fractions (with Smartypants)

SmartypantsDashes // Enable smart dashes (with Smartypants) SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering + SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants) TOC // Generate a table of contents TagName = "[A-Za-z][A-Za-z0-9-]*"
M inline_test.goinline_test.go

@@ -1034,6 +1034,18 @@

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) { var tests = []string{ "this should be angled \"quoted\" text.\n",

@@ -1044,6 +1056,18 @@ "two pair of \"some\" quoted \"text\".\n",

"<p>two pair of &laquo;some&raquo; quoted &laquo;text&raquo;.</p>\n"} 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) {

@@ -1140,3 +1164,13 @@ "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) + } +}
M smartypants.gosmartypants.go

@@ -42,7 +42,7 @@ func isdigit(c byte) bool {

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, // so we treat it like text sometimes

@@ -97,6 +97,12 @@ *isOpen = true

default: // [a'b] maybe a contraction? *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('&')

@@ -107,6 +113,11 @@ out.WriteByte('r')

} out.WriteByte(quote) out.WriteString("quo;") + + if addNBSP && *isOpen { + out.WriteString("&nbsp;") + } + return true }

@@ -119,7 +130,7 @@ nextChar := byte(0)

if len(text) >= 3 { nextChar = text[2] } - if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) { + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { return 1 } }

@@ -144,7 +155,7 @@ nextChar := byte(0)

if len(text) > 1 { nextChar = text[1] } - if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote) { + if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { return 0 }

@@ -208,13 +219,13 @@ out.WriteByte(text[0])

return 0 } -func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, 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;")) { nextChar := byte(0) if len(text) >= 7 { nextChar = text[6] } - if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) { + if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { return 5 } }

@@ -227,12 +238,15 @@ out.WriteByte('&')

return 0 } -func (r *SPRenderer) smartAmp(out *bytes.Buffer, previousChar byte, text []byte) int { - return r.smartAmpVariant(out, previousChar, text, 'd') -} +func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { + var quote byte = 'd' + if angledQuotes { + quote = 'a' + } -func (r *SPRenderer) smartAmpAngledQuote(out *bytes.Buffer, previousChar byte, text []byte) int { - return r.smartAmpVariant(out, previousChar, text, 'a') + return func(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) + } } func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {

@@ -256,7 +270,7 @@ nextChar := byte(0)

if len(text) >= 3 { nextChar = text[2] } - if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) { + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { return 1 } }

@@ -340,7 +354,7 @@ nextChar := byte(0)

if len(text) > 1 { nextChar = text[1] } - if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) { + if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { out.WriteString("&quot;") }

@@ -370,13 +384,31 @@ type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int

// NewSmartypantsRenderer constructs a Smartypants renderer object. func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { - var r SPRenderer + var ( + r SPRenderer + + smartAmpAngled = r.smartAmp(true, false) + smartAmpAngledNBSP = r.smartAmp(true, true) + smartAmpRegular = r.smartAmp(false, false) + smartAmpRegularNBSP = r.smartAmp(false, true) + + addNBSP = flags&SmartypantsQuotesNBSP != 0 + ) + if flags&SmartypantsAngledQuotes == 0 { r.callbacks['"'] = r.smartDoubleQuote - r.callbacks['&'] = r.smartAmp + if !addNBSP { + r.callbacks['&'] = smartAmpRegular + } else { + r.callbacks['&'] = smartAmpRegularNBSP + } } else { r.callbacks['"'] = r.smartAngledDoubleQuote - r.callbacks['&'] = r.smartAmpAngledQuote + if !addNBSP { + r.callbacks['&'] = smartAmpAngled + } else { + r.callbacks['&'] = smartAmpAngledNBSP + } } r.callbacks['\''] = r.smartSingleQuote r.callbacks['('] = r.smartParens