Skip to content

Commit cadec56

Browse files
authored
Merge pull request #384 from bep/v2
Add Smartypants support for French Guillemets
2 parents 4582051 + 3a1d515 commit cadec56

File tree

3 files changed

+82
-15
lines changed

3 files changed

+82
-15
lines changed

html.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
SmartypantsDashes // Enable smart dashes (with Smartypants)
4545
SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
4646
SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
47+
SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
4748
TOC // Generate a table of contents
4849
)
4950

inline_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
10341034
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants})
10351035
}
10361036

1037+
func TestSmartDoubleQuotesNBSP(t *testing.T) {
1038+
var tests = []string{
1039+
"this should be normal \"quoted\" text.\n",
1040+
"<p>this should be normal &ldquo;&nbsp;quoted&nbsp;&rdquo; text.</p>\n",
1041+
"this \" single double\n",
1042+
"<p>this &ldquo;&nbsp; single double</p>\n",
1043+
"two pair of \"some\" quoted \"text\".\n",
1044+
"<p>two pair of &ldquo;&nbsp;some&nbsp;&rdquo; quoted &ldquo;&nbsp;text&nbsp;&rdquo;.</p>\n"}
1045+
1046+
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsQuotesNBSP})
1047+
}
1048+
10371049
func TestSmartAngledDoubleQuotes(t *testing.T) {
10381050
var tests = []string{
10391051
"this should be angled \"quoted\" text.\n",
@@ -1046,6 +1058,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
10461058
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes})
10471059
}
10481060

1061+
func TestSmartAngledDoubleQuotesNBSP(t *testing.T) {
1062+
var tests = []string{
1063+
"this should be angled \"quoted\" text.\n",
1064+
"<p>this should be angled &laquo;&nbsp;quoted&nbsp;&raquo; text.</p>\n",
1065+
"this \" single double\n",
1066+
"<p>this &laquo;&nbsp; single double</p>\n",
1067+
"two pair of \"some\" quoted \"text\".\n",
1068+
"<p>two pair of &laquo;&nbsp;some&nbsp;&raquo; quoted &laquo;&nbsp;text&nbsp;&raquo;.</p>\n"}
1069+
1070+
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes | SmartypantsQuotesNBSP})
1071+
}
1072+
10491073
func TestSmartFractions(t *testing.T) {
10501074
var tests = []string{
10511075
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
@@ -1140,3 +1164,13 @@ func TestSkipHTML(t *testing.T) {
11401164
"<p>text inline html more text</p>\n",
11411165
}, TestParams{HTMLFlags: SkipHTML})
11421166
}
1167+
1168+
func BenchmarkSmartDoubleQuotes(b *testing.B) {
1169+
params := TestParams{HTMLFlags: Smartypants}
1170+
params.extensions |= Autolink | Strikethrough
1171+
params.HTMLFlags |= UseXHTML
1172+
1173+
for i := 0; i < b.N; i++ {
1174+
runMarkdown("this should be normal \"quoted\" text.\n", params)
1175+
}
1176+
}

smartypants.go

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func isdigit(c byte) bool {
4242
return c >= '0' && c <= '9'
4343
}
4444

45-
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
45+
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
4646
// edge of the buffer is likely to be a tag that we don't get to see,
4747
// so we treat it like text sometimes
4848

@@ -99,6 +99,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
9999
*isOpen = false
100100
}
101101

102+
// Note that with the limited lookahead, this non-breaking
103+
// space will also be appended to single double quotes.
104+
if addNBSP && !*isOpen {
105+
out.WriteString("&nbsp;")
106+
}
107+
102108
out.WriteByte('&')
103109
if *isOpen {
104110
out.WriteByte('l')
@@ -107,6 +113,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
107113
}
108114
out.WriteByte(quote)
109115
out.WriteString("quo;")
116+
117+
if addNBSP && *isOpen {
118+
out.WriteString("&nbsp;")
119+
}
120+
110121
return true
111122
}
112123

@@ -119,7 +130,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
119130
if len(text) >= 3 {
120131
nextChar = text[2]
121132
}
122-
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) {
133+
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
123134
return 1
124135
}
125136
}
@@ -144,7 +155,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
144155
if len(text) > 1 {
145156
nextChar = text[1]
146157
}
147-
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote) {
158+
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
148159
return 0
149160
}
150161

@@ -208,13 +219,13 @@ func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text [
208219
return 0
209220
}
210221

211-
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
222+
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
212223
if bytes.HasPrefix(text, []byte("&quot;")) {
213224
nextChar := byte(0)
214225
if len(text) >= 7 {
215226
nextChar = text[6]
216227
}
217-
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) {
228+
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
218229
return 5
219230
}
220231
}
@@ -227,12 +238,15 @@ func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text
227238
return 0
228239
}
229240

230-
func (r *SPRenderer) smartAmp(out *bytes.Buffer, previousChar byte, text []byte) int {
231-
return r.smartAmpVariant(out, previousChar, text, 'd')
232-
}
241+
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
242+
var quote byte = 'd'
243+
if angledQuotes {
244+
quote = 'a'
245+
}
233246

234-
func (r *SPRenderer) smartAmpAngledQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
235-
return r.smartAmpVariant(out, previousChar, text, 'a')
247+
return func(out *bytes.Buffer, previousChar byte, text []byte) int {
248+
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
249+
}
236250
}
237251

238252
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 []
256270
if len(text) >= 3 {
257271
nextChar = text[2]
258272
}
259-
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) {
273+
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
260274
return 1
261275
}
262276
}
@@ -340,7 +354,7 @@ func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byt
340354
if len(text) > 1 {
341355
nextChar = text[1]
342356
}
343-
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) {
357+
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
344358
out.WriteString("&quot;")
345359
}
346360

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

371385
// NewSmartypantsRenderer constructs a Smartypants renderer object.
372386
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
373-
var r SPRenderer
387+
var (
388+
r SPRenderer
389+
390+
smartAmpAngled = r.smartAmp(true, false)
391+
smartAmpAngledNBSP = r.smartAmp(true, true)
392+
smartAmpRegular = r.smartAmp(false, false)
393+
smartAmpRegularNBSP = r.smartAmp(false, true)
394+
395+
addNBSP = flags&SmartypantsQuotesNBSP != 0
396+
)
397+
374398
if flags&SmartypantsAngledQuotes == 0 {
375399
r.callbacks['"'] = r.smartDoubleQuote
376-
r.callbacks['&'] = r.smartAmp
400+
if !addNBSP {
401+
r.callbacks['&'] = smartAmpRegular
402+
} else {
403+
r.callbacks['&'] = smartAmpRegularNBSP
404+
}
377405
} else {
378406
r.callbacks['"'] = r.smartAngledDoubleQuote
379-
r.callbacks['&'] = r.smartAmpAngledQuote
407+
if !addNBSP {
408+
r.callbacks['&'] = smartAmpAngled
409+
} else {
410+
r.callbacks['&'] = smartAmpAngledNBSP
411+
}
380412
}
381413
r.callbacks['\''] = r.smartSingleQuote
382414
r.callbacks['('] = r.smartParens

0 commit comments

Comments
 (0)