Skip to content

Commit 4048872

Browse files
authored
Merge pull request #379 from bep/guillemets
Add Smartypants support for French Guillemets
2 parents 067529f + 4ca8c28 commit 4048872

File tree

3 files changed

+75
-14
lines changed

3 files changed

+75
-14
lines changed

html.go

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const (
4242
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
4343
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
4444
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
45+
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
4546
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
4647
)
4748

inline_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
11731173
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
11741174
}
11751175

1176+
func TestSmartDoubleQuotesNbsp(t *testing.T) {
1177+
var tests = []string{
1178+
"this should be normal \"quoted\" text.\n",
1179+
"<p>this should be normal &ldquo;&nbsp;quoted&nbsp;&rdquo; text.</p>\n",
1180+
"this \" single double\n",
1181+
"<p>this &ldquo;&nbsp; single double</p>\n",
1182+
"two pair of \"some\" quoted \"text\".\n",
1183+
"<p>two pair of &ldquo;&nbsp;some&nbsp;&rdquo; quoted &ldquo;&nbsp;text&nbsp;&rdquo;.</p>\n"}
1184+
1185+
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
1186+
}
1187+
11761188
func TestSmartAngledDoubleQuotes(t *testing.T) {
11771189
var tests = []string{
11781190
"this should be angled \"quoted\" text.\n",
@@ -1185,6 +1197,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
11851197
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
11861198
}
11871199

1200+
func TestSmartAngledDoubleQuotesNbsp(t *testing.T) {
1201+
var tests = []string{
1202+
"this should be angled \"quoted\" text.\n",
1203+
"<p>this should be angled &laquo;&nbsp;quoted&nbsp;&raquo; text.</p>\n",
1204+
"this \" single double\n",
1205+
"<p>this &laquo;&nbsp; single double</p>\n",
1206+
"two pair of \"some\" quoted \"text\".\n",
1207+
"<p>two pair of &laquo;&nbsp;some&nbsp;&raquo; quoted &laquo;&nbsp;text&nbsp;&raquo;.</p>\n"}
1208+
1209+
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
1210+
}
1211+
11881212
func TestSmartFractions(t *testing.T) {
11891213
var tests = []string{
11901214
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
@@ -1240,3 +1264,9 @@ func TestDisableSmartDashes(t *testing.T) {
12401264
HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
12411265
HtmlRendererParameters{})
12421266
}
1267+
1268+
func BenchmarkSmartDoubleQuotes(b *testing.B) {
1269+
for i := 0; i < b.N; i++ {
1270+
runMarkdownInline("this should be normal \"quoted\" text.\n", Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
1271+
}
1272+
}

smartypants.go

+44-14
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func isdigit(c byte) bool {
3939
return c >= '0' && c <= '9'
4040
}
4141

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

@@ -96,6 +96,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
9696
*isOpen = false
9797
}
9898

99+
// Note that with the limited lookahead, this non-breaking
100+
// space will also be appended to single double quotes.
101+
if addNBSP && !*isOpen {
102+
out.WriteString("&nbsp;")
103+
}
104+
99105
out.WriteByte('&')
100106
if *isOpen {
101107
out.WriteByte('l')
@@ -104,6 +110,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
104110
}
105111
out.WriteByte(quote)
106112
out.WriteString("quo;")
113+
114+
if addNBSP && *isOpen {
115+
out.WriteString("&nbsp;")
116+
}
117+
107118
return true
108119
}
109120

@@ -116,7 +127,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
116127
if len(text) >= 3 {
117128
nextChar = text[2]
118129
}
119-
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
130+
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
120131
return 1
121132
}
122133
}
@@ -141,7 +152,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
141152
if len(text) > 1 {
142153
nextChar = text[1]
143154
}
144-
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
155+
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
145156
return 0
146157
}
147158

@@ -205,13 +216,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
205216
return 0
206217
}
207218

208-
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
219+
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
209220
if bytes.HasPrefix(text, []byte("&quot;")) {
210221
nextChar := byte(0)
211222
if len(text) >= 7 {
212223
nextChar = text[6]
213224
}
214-
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
225+
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
215226
return 5
216227
}
217228
}
@@ -224,12 +235,15 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
224235
return 0
225236
}
226237

227-
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
228-
return smartAmpVariant(out, smrt, previousChar, text, 'd')
229-
}
238+
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
239+
var quote byte = 'd'
240+
if angledQuotes {
241+
quote = 'a'
242+
}
230243

231-
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
232-
return smartAmpVariant(out, smrt, previousChar, text, 'a')
244+
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
245+
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
246+
}
233247
}
234248

235249
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
@@ -253,7 +267,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
253267
if len(text) >= 3 {
254268
nextChar = text[2]
255269
}
256-
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
270+
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
257271
return 1
258272
}
259273
}
@@ -337,7 +351,7 @@ func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousC
337351
if len(text) > 1 {
338352
nextChar = text[1]
339353
}
340-
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
354+
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
341355
out.WriteString("&quot;")
342356
}
343357

@@ -367,14 +381,30 @@ type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar b
367381

368382
type smartypantsRenderer [256]smartCallback
369383

384+
var (
385+
smartAmpAngled = smartAmp(true, false)
386+
smartAmpAngledNBSP = smartAmp(true, true)
387+
smartAmpRegular = smartAmp(false, false)
388+
smartAmpRegularNBSP = smartAmp(false, true)
389+
)
390+
370391
func smartypants(flags int) *smartypantsRenderer {
371392
r := new(smartypantsRenderer)
393+
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
372394
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
373395
r['"'] = smartDoubleQuote
374-
r['&'] = smartAmp
396+
if !addNBSP {
397+
r['&'] = smartAmpRegular
398+
} else {
399+
r['&'] = smartAmpRegularNBSP
400+
}
375401
} else {
376402
r['"'] = smartAngledDoubleQuote
377-
r['&'] = smartAmpAngledQuote
403+
if !addNBSP {
404+
r['&'] = smartAmpAngled
405+
} else {
406+
r['&'] = smartAmpAngledNBSP
407+
}
378408
}
379409
r['\''] = smartSingleQuote
380410
r['('] = smartParens

0 commit comments

Comments
 (0)