smartypants.go (view raw)
1//
2// Black Friday Markdown Processor
3// Originally based on http://github.com/tanoku/upskirt
4// by Russ Ross <russ@russross.com>
5//
6
7//
8//
9// SmartyPants rendering
10//
11//
12
13package blackfriday
14
15import (
16 "bytes"
17)
18
19type smartypants_data struct {
20 in_squote bool
21 in_dquote bool
22}
23
24func word_boundary(c byte) bool {
25 return c == 0 || isspace(c) || ispunct(c)
26}
27
28func tolower(c byte) byte {
29 if c >= 'A' && c <= 'Z' {
30 return c - 'A' + 'a'
31 }
32 return c
33}
34
35func isdigit(c byte) bool {
36 return c >= '0' && c <= '9'
37}
38
39func smartypants_quotes(ob *bytes.Buffer, previous_char byte, next_char byte, quote byte, is_open *bool) bool {
40 switch {
41 // edge of the buffer is likely to be a tag that we don't get to see,
42 // so we assume there is text there
43 case word_boundary(previous_char) && previous_char != 0 && next_char == 0:
44 *is_open = true
45 case previous_char == 0 && word_boundary(next_char) && next_char != 0:
46 *is_open = false
47 case word_boundary(previous_char) && !word_boundary(next_char):
48 *is_open = true
49 case !word_boundary(previous_char) && word_boundary(next_char):
50 *is_open = false
51 case !word_boundary(previous_char) && !word_boundary(next_char):
52 *is_open = true
53 default:
54 *is_open = !*is_open
55 }
56
57 ob.WriteByte('&')
58 if *is_open {
59 ob.WriteByte('l')
60 } else {
61 ob.WriteByte('r')
62 }
63 ob.WriteByte(quote)
64 ob.WriteString("quo;")
65 return true
66}
67
68func smartypants_cb__squote(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
69 if len(text) >= 2 {
70 t1 := tolower(text[1])
71
72 if t1 == '\'' {
73 next_char := byte(0)
74 if len(text) >= 3 {
75 next_char = text[2]
76 }
77 if smartypants_quotes(ob, previous_char, next_char, 'd', &smrt.in_dquote) {
78 return 1
79 }
80 }
81
82 if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || word_boundary(text[2])) {
83 ob.WriteString("’")
84 return 0
85 }
86
87 if len(text) >= 3 {
88 t2 := tolower(text[2])
89
90 if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && (len(text) < 4 || word_boundary(text[3])) {
91 ob.WriteString("’")
92 return 0
93 }
94 }
95 }
96
97 next_char := byte(0)
98 if len(text) > 1 {
99 next_char = text[1]
100 }
101 if smartypants_quotes(ob, previous_char, next_char, 's', &smrt.in_squote) {
102 return 0
103 }
104
105 ob.WriteByte(text[0])
106 return 0
107}
108
109func smartypants_cb__parens(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
110 if len(text) >= 3 {
111 t1 := tolower(text[1])
112 t2 := tolower(text[2])
113
114 if t1 == 'c' && t2 == ')' {
115 ob.WriteString("©")
116 return 2
117 }
118
119 if t1 == 'r' && t2 == ')' {
120 ob.WriteString("®")
121 return 2
122 }
123
124 if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
125 ob.WriteString("™")
126 return 3
127 }
128 }
129
130 ob.WriteByte(text[0])
131 return 0
132}
133
134func smartypants_cb__dash(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
135 if len(text) >= 2 {
136 if text[1] == '-' {
137 ob.WriteString("—")
138 return 1
139 }
140
141 if word_boundary(previous_char) && word_boundary(text[1]) {
142 ob.WriteString("–")
143 return 0
144 }
145 }
146
147 ob.WriteByte(text[0])
148 return 0
149}
150
151func smartypants_cb__dash_latex(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
152 if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
153 ob.WriteString("—")
154 return 2
155 }
156 if len(text) >= 2 && text[1] == '-' {
157 ob.WriteString("–")
158 return 1
159 }
160
161 ob.WriteByte(text[0])
162 return 0
163}
164
165func smartypants_cb__amp(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
166 if bytes.HasPrefix(text, []byte(""")) {
167 next_char := byte(0)
168 if len(text) >= 7 {
169 next_char = text[6]
170 }
171 if smartypants_quotes(ob, previous_char, next_char, 'd', &smrt.in_dquote) {
172 return 5
173 }
174 }
175
176 if bytes.HasPrefix(text, []byte("�")) {
177 return 3
178 }
179
180 ob.WriteByte('&')
181 return 0
182}
183
184func smartypants_cb__period(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
185 if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
186 ob.WriteString("…")
187 return 2
188 }
189
190 if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
191 ob.WriteString("…")
192 return 4
193 }
194
195 ob.WriteByte(text[0])
196 return 0
197}
198
199func smartypants_cb__backtick(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
200 if len(text) >= 2 && text[1] == '`' {
201 next_char := byte(0)
202 if len(text) >= 3 {
203 next_char = text[2]
204 }
205 if smartypants_quotes(ob, previous_char, next_char, 'd', &smrt.in_dquote) {
206 return 1
207 }
208 }
209
210 return 0
211}
212
213func smartypants_cb__number_generic(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
214 if word_boundary(previous_char) && len(text) >= 3 {
215 // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
216 num_end := 0
217 for len(text) > num_end && isdigit(text[num_end]) {
218 num_end++
219 }
220 if num_end == 0 {
221 return 0
222 }
223 if len(text) < num_end+2 || text[num_end] != '/' {
224 return 0
225 }
226 den_end := num_end + 1
227 for len(text) > den_end && isdigit(text[den_end]) {
228 den_end++
229 }
230 if den_end == num_end+1 {
231 return 0
232 }
233 if len(text) == den_end || word_boundary(text[den_end]) {
234 ob.WriteString("<sup>")
235 ob.Write(text[:num_end])
236 ob.WriteString("</sup>⁄<sub>")
237 ob.Write(text[num_end+1 : den_end])
238 ob.WriteString("</sub>")
239 return den_end - 1
240 }
241 }
242
243 ob.WriteByte(text[0])
244 return 0
245}
246
247func smartypants_cb__number(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
248 if word_boundary(previous_char) && len(text) >= 3 {
249 if text[0] == '1' && text[1] == '/' && text[2] == '2' {
250 if len(text) < 4 || word_boundary(text[3]) {
251 ob.WriteString("½")
252 return 2
253 }
254 }
255
256 if text[0] == '1' && text[1] == '/' && text[2] == '4' {
257 if len(text) < 4 || word_boundary(text[3]) || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
258 ob.WriteString("¼")
259 return 2
260 }
261 }
262
263 if text[0] == '3' && text[1] == '/' && text[2] == '4' {
264 if len(text) < 4 || word_boundary(text[3]) || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
265 ob.WriteString("¾")
266 return 2
267 }
268 }
269 }
270
271 ob.WriteByte(text[0])
272 return 0
273}
274
275func smartypants_cb__dquote(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
276 next_char := byte(0)
277 if len(text) > 1 {
278 next_char = text[1]
279 }
280 if !smartypants_quotes(ob, previous_char, next_char, 'd', &smrt.in_dquote) {
281 ob.WriteString(""")
282 }
283
284 return 0
285}
286
287func smartypants_cb__ltag(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int {
288 i := 0
289
290 for i < len(text) && text[i] != '>' {
291 i++
292 }
293
294 ob.Write(text[:i+1])
295 return i
296}
297
298type smartypants_cb func(ob *bytes.Buffer, smrt *smartypants_data, previous_char byte, text []byte) int
299
300type SmartypantsRenderer [256]smartypants_cb
301
302func Smartypants(flags int) *SmartypantsRenderer {
303 r := new(SmartypantsRenderer)
304 r['"'] = smartypants_cb__dquote
305 r['&'] = smartypants_cb__amp
306 r['\''] = smartypants_cb__squote
307 r['('] = smartypants_cb__parens
308 if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
309 r['-'] = smartypants_cb__dash
310 } else {
311 r['-'] = smartypants_cb__dash_latex
312 }
313 r['.'] = smartypants_cb__period
314 if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
315 r['1'] = smartypants_cb__number
316 r['3'] = smartypants_cb__number
317 } else {
318 for ch := '1'; ch <= '9'; ch++ {
319 r[ch] = smartypants_cb__number_generic
320 }
321 }
322 r['<'] = smartypants_cb__ltag
323 r['`'] = smartypants_cb__backtick
324 return r
325}
326
327func rndr_smartypants(ob *bytes.Buffer, text []byte, opaque interface{}) {
328 options := opaque.(*htmlOptions)
329 smrt := smartypants_data{false, false}
330
331 mark := 0
332 for i := 0; i < len(text); i++ {
333 if action := options.smartypants[text[i]]; action != nil {
334 if i > mark {
335 ob.Write(text[mark:i])
336 }
337
338 previous_char := byte(0)
339 if i > 0 {
340 previous_char = text[i-1]
341 }
342 i += action(ob, &smrt, previous_char, text[i:])
343 mark = i + 1
344 }
345 }
346
347 if mark < len(text) {
348 ob.Write(text[mark:])
349 }
350}