1
0
mirror of https://github.com/danog/blackfriday.git synced 2024-11-30 04:29:13 +01:00

Add Smartypants support for French Guillemets

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

Fixes #380
This commit is contained in:
Bjørn Erik Pedersen 2017-07-31 17:40:36 +02:00
parent f42ca5bf18
commit 3a1d515242
3 changed files with 82 additions and 15 deletions

View File

@ -44,6 +44,7 @@ const (
SmartypantsDashes // Enable smart dashes (with Smartypants) SmartypantsDashes // Enable smart dashes (with Smartypants)
SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
TOC // Generate a table of contents TOC // Generate a table of contents
TagName = "[A-Za-z][A-Za-z0-9-]*" TagName = "[A-Za-z][A-Za-z0-9-]*"

View File

@ -1034,6 +1034,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants}) 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) {
var tests = []string{ var tests = []string{
"this should be angled \"quoted\" text.\n", "this should be angled \"quoted\" text.\n",
@ -1046,6 +1058,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes}) 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) {
var tests = []string{ var tests = []string{
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n", "1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
@ -1140,3 +1164,13 @@ func TestSkipHTML(t *testing.T) {
"<p>text inline html more text</p>\n", "<p>text inline html more text</p>\n",
}, TestParams{HTMLFlags: SkipHTML}) }, 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

@ -42,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
@ -99,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')
@ -107,6 +113,11 @@ 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
} }
@ -119,7 +130,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -144,7 +155,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
return 0 return 0
} }
@ -208,13 +219,13 @@ func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text [
return 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;")) { 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, &r.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
return 5 return 5
} }
} }
@ -227,12 +238,15 @@ func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text
return 0 return 0
} }
func (r *SPRenderer) smartAmp(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
return r.smartAmpVariant(out, previousChar, text, 'd') var quote byte = 'd'
} if angledQuotes {
quote = 'a'
}
func (r *SPRenderer) smartAmpAngledQuote(out *bytes.Buffer, previousChar byte, text []byte) int { return func(out *bytes.Buffer, previousChar byte, text []byte) int {
return r.smartAmpVariant(out, previousChar, text, 'a') return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
}
} }
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
@ -256,7 +270,7 @@ func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -340,7 +354,7 @@ func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byt
if len(text) > 1 { if len(text) > 1 {
nextChar = 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;") out.WriteString("&quot;")
} }
@ -370,13 +384,31 @@ type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
// NewSmartypantsRenderer constructs a Smartypants renderer object. // NewSmartypantsRenderer constructs a Smartypants renderer object.
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { 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 { if flags&SmartypantsAngledQuotes == 0 {
r.callbacks['"'] = r.smartDoubleQuote r.callbacks['"'] = r.smartDoubleQuote
r.callbacks['&'] = r.smartAmp if !addNBSP {
r.callbacks['&'] = smartAmpRegular
} else {
r.callbacks['&'] = smartAmpRegularNBSP
}
} else { } else {
r.callbacks['"'] = r.smartAngledDoubleQuote r.callbacks['"'] = r.smartAngledDoubleQuote
r.callbacks['&'] = r.smartAmpAngledQuote if !addNBSP {
r.callbacks['&'] = smartAmpAngled
} else {
r.callbacks['&'] = smartAmpAngledNBSP
}
} }
r.callbacks['\''] = r.smartSingleQuote r.callbacks['\''] = r.smartSingleQuote
r.callbacks['('] = r.smartParens r.callbacks['('] = r.smartParens