Merge pull request #384 from bep/v2 Add Smartypants support for French Guillemets
Vytautas Šaltenis vytas@rtfb.lt
Sun, 06 Aug 2017 20:10:14 +0300
3 files changed,
82 insertions(+),
15 deletions(-)
M
html.go
→
html.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 )
M
inline_test.go
→
inline_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 “ quoted ” text.</p>\n", + "this \" single double\n", + "<p>this “ single double</p>\n", + "two pair of \"some\" quoted \"text\".\n", + "<p>two pair of “ some ” quoted “ text ”.</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 «some» quoted «text».</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 « quoted » text.</p>\n", + "this \" single double\n", + "<p>this « single double</p>\n", + "two pair of \"some\" quoted \"text\".\n", + "<p>two pair of « some » quoted « text ».</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.go
→
smartypants.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(" ") } out.WriteByte('&')@@ -107,6 +113,11 @@ out.WriteByte('r')
} out.WriteByte(quote) out.WriteString("quo;") + + if addNBSP && *isOpen { + out.WriteString(" ") + } + 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(""")) { 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(""") }@@ -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