Skip to content

Commit

Permalink
Merge pull request #379 from bep/guillemets
Browse files Browse the repository at this point in the history
Add Smartypants support for French Guillemets
  • Loading branch information
rtfb committed Jul 28, 2017
2 parents 067529f + 4ca8c28 commit 4048872
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 14 deletions.
1 change: 1 addition & 0 deletions html.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
)

Expand Down
30 changes: 30 additions & 0 deletions inline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
}

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, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
}

func TestSmartAngledDoubleQuotes(t *testing.T) {
var tests = []string{
"this should be angled \"quoted\" text.\n",
Expand All @@ -1185,6 +1197,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
}

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, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
}

func TestSmartFractions(t *testing.T) {
var tests = []string{
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
Expand Down Expand Up @@ -1240,3 +1264,9 @@ func TestDisableSmartDashes(t *testing.T) {
HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
HtmlRendererParameters{})
}

func BenchmarkSmartDoubleQuotes(b *testing.B) {
for i := 0; i < b.N; i++ {
runMarkdownInline("this should be normal \"quoted\" text.\n", Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
}
}
58 changes: 44 additions & 14 deletions smartypants.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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

Expand Down Expand Up @@ -96,6 +96,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
*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('&')
if *isOpen {
out.WriteByte('l')
Expand All @@ -104,6 +110,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
}
out.WriteByte(quote)
out.WriteString("quo;")

if addNBSP && *isOpen {
out.WriteString("&nbsp;")
}

return true
}

Expand All @@ -116,7 +127,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
if len(text) >= 3 {
nextChar = text[2]
}
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
return 1
}
}
Expand All @@ -141,7 +152,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
if len(text) > 1 {
nextChar = text[1]
}
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
return 0
}

Expand Down Expand Up @@ -205,13 +216,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
return 0
}

func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, 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, &smrt.inDoubleQuote) {
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
return 5
}
}
Expand All @@ -224,12 +235,15 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
return 0
}

func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, 'd')
}
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
var quote byte = 'd'
if angledQuotes {
quote = 'a'
}

func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, 'a')
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
}
}

func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
Expand All @@ -253,7 +267,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
if len(text) >= 3 {
nextChar = text[2]
}
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
return 1
}
}
Expand Down Expand Up @@ -337,7 +351,7 @@ func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousC
if len(text) > 1 {
nextChar = text[1]
}
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
out.WriteString("&quot;")
}

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

type smartypantsRenderer [256]smartCallback

var (
smartAmpAngled = smartAmp(true, false)
smartAmpAngledNBSP = smartAmp(true, true)
smartAmpRegular = smartAmp(false, false)
smartAmpRegularNBSP = smartAmp(false, true)
)

func smartypants(flags int) *smartypantsRenderer {
r := new(smartypantsRenderer)
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
r['"'] = smartDoubleQuote
r['&'] = smartAmp
if !addNBSP {
r['&'] = smartAmpRegular
} else {
r['&'] = smartAmpRegularNBSP
}
} else {
r['"'] = smartAngledDoubleQuote
r['&'] = smartAmpAngledQuote
if !addNBSP {
r['&'] = smartAmpAngled
} else {
r['&'] = smartAmpAngledNBSP
}
}
r['\''] = smartSingleQuote
r['('] = smartParens
Expand Down

0 comments on commit 4048872

Please # to comment.