all repos — grayfriday @ 6c6efa13b561c6b7541bce781b5472a8e1c59492

blackfriday fork with a few changes

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("&rsquo;")
 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("&rsquo;")
 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("&copy;")
116			return 2
117		}
118
119		if t1 == 'r' && t2 == ')' {
120			ob.WriteString("&reg;")
121			return 2
122		}
123
124		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
125			ob.WriteString("&trade;")
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("&mdash;")
138			return 1
139		}
140
141		if word_boundary(previous_char) && word_boundary(text[1]) {
142			ob.WriteString("&ndash;")
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("&mdash;")
154		return 2
155	}
156	if len(text) >= 2 && text[1] == '-' {
157		ob.WriteString("&ndash;")
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("&quot;")) {
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("&#0;")) {
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("&hellip;")
187		return 2
188	}
189
190	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
191		ob.WriteString("&hellip;")
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>&frasl;<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("&frac12;")
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("&frac14;")
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("&frac34;")
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("&quot;")
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}