all repos — grayfriday @ 2f988eb278d42a4a3dcf7955252e341ba1a1826d

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 smartypantsData struct {
 20	inSingleQuote bool
 21	inDoubleQuote bool
 22}
 23
 24func wordBoundary(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 smartQuotesHelper(ob *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *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 wordBoundary(previousChar) && previousChar != 0 && nextChar == 0:
 44		*isOpen = true
 45	case previousChar == 0 && wordBoundary(nextChar) && nextChar != 0:
 46		*isOpen = false
 47	case wordBoundary(previousChar) && !wordBoundary(nextChar):
 48		*isOpen = true
 49	case !wordBoundary(previousChar) && wordBoundary(nextChar):
 50		*isOpen = false
 51	case !wordBoundary(previousChar) && !wordBoundary(nextChar):
 52		*isOpen = true
 53	default:
 54		*isOpen = !*isOpen
 55	}
 56
 57	ob.WriteByte('&')
 58	if *isOpen {
 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 smartSquote(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
 69	if len(text) >= 2 {
 70		t1 := tolower(text[1])
 71
 72		if t1 == '\'' {
 73			nextChar := byte(0)
 74			if len(text) >= 3 {
 75				nextChar = text[2]
 76			}
 77			if smartQuotesHelper(ob, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
 78				return 1
 79			}
 80		}
 81
 82		if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(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 || wordBoundary(text[3])) {
 91				ob.WriteString("&rsquo;")
 92				return 0
 93			}
 94		}
 95	}
 96
 97	nextChar := byte(0)
 98	if len(text) > 1 {
 99		nextChar = text[1]
100	}
101	if smartQuotesHelper(ob, previousChar, nextChar, 's', &smrt.inSingleQuote) {
102		return 0
103	}
104
105	ob.WriteByte(text[0])
106	return 0
107}
108
109func smartParens(ob *bytes.Buffer, smrt *smartypantsData, previousChar 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 smartDash(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
135	if len(text) >= 2 {
136		if text[1] == '-' {
137			ob.WriteString("&mdash;")
138			return 1
139		}
140
141		if wordBoundary(previousChar) && wordBoundary(text[1]) {
142			ob.WriteString("&ndash;")
143			return 0
144		}
145	}
146
147	ob.WriteByte(text[0])
148	return 0
149}
150
151func smartDashLatex(ob *bytes.Buffer, smrt *smartypantsData, previousChar 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 smartAmp(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
166	if bytes.HasPrefix(text, []byte("&quot;")) {
167		nextChar := byte(0)
168		if len(text) >= 7 {
169			nextChar = text[6]
170		}
171		if smartQuotesHelper(ob, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
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 smartPeriod(ob *bytes.Buffer, smrt *smartypantsData, previousChar 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 smartBacktick(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
200	if len(text) >= 2 && text[1] == '`' {
201		nextChar := byte(0)
202		if len(text) >= 3 {
203			nextChar = text[2]
204		}
205		if smartQuotesHelper(ob, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
206			return 1
207		}
208	}
209
210	return 0
211}
212
213func smartNumberGeneric(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
214	if wordBoundary(previousChar) && 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			ob.WriteByte(text[0])
222			return 0
223		}
224		if len(text) < num_end+2 || text[num_end] != '/' {
225			ob.WriteByte(text[0])
226			return 0
227		}
228		den_end := num_end + 1
229		for len(text) > den_end && isdigit(text[den_end]) {
230			den_end++
231		}
232		if den_end == num_end+1 {
233			ob.WriteByte(text[0])
234			return 0
235		}
236		if len(text) == den_end || wordBoundary(text[den_end]) {
237			ob.WriteString("<sup>")
238			ob.Write(text[:num_end])
239			ob.WriteString("</sup>&frasl;<sub>")
240			ob.Write(text[num_end+1 : den_end])
241			ob.WriteString("</sub>")
242			return den_end - 1
243		}
244	}
245
246	ob.WriteByte(text[0])
247	return 0
248}
249
250func smartNumber(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
251	if wordBoundary(previousChar) && len(text) >= 3 {
252		if text[0] == '1' && text[1] == '/' && text[2] == '2' {
253			if len(text) < 4 || wordBoundary(text[3]) {
254				ob.WriteString("&frac12;")
255				return 2
256			}
257		}
258
259		if text[0] == '1' && text[1] == '/' && text[2] == '4' {
260			if len(text) < 4 || wordBoundary(text[3]) || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
261				ob.WriteString("&frac14;")
262				return 2
263			}
264		}
265
266		if text[0] == '3' && text[1] == '/' && text[2] == '4' {
267			if len(text) < 4 || wordBoundary(text[3]) || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
268				ob.WriteString("&frac34;")
269				return 2
270			}
271		}
272	}
273
274	ob.WriteByte(text[0])
275	return 0
276}
277
278func smartDquote(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
279	nextChar := byte(0)
280	if len(text) > 1 {
281		nextChar = text[1]
282	}
283	if !smartQuotesHelper(ob, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
284		ob.WriteString("&quot;")
285	}
286
287	return 0
288}
289
290func smartLtag(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
291	i := 0
292
293	for i < len(text) && text[i] != '>' {
294		i++
295	}
296
297	ob.Write(text[:i+1])
298	return i
299}
300
301type smartCallback func(ob *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
302
303type SmartypantsRenderer [256]smartCallback
304
305func Smartypants(flags int) *SmartypantsRenderer {
306	r := new(SmartypantsRenderer)
307	r['"'] = smartDquote
308	r['&'] = smartAmp
309	r['\''] = smartSquote
310	r['('] = smartParens
311	if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
312		r['-'] = smartDash
313	} else {
314		r['-'] = smartDashLatex
315	}
316	r['.'] = smartPeriod
317	if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
318		r['1'] = smartNumber
319		r['3'] = smartNumber
320	} else {
321		for ch := '1'; ch <= '9'; ch++ {
322			r[ch] = smartNumberGeneric
323		}
324	}
325	r['<'] = smartLtag
326	r['`'] = smartBacktick
327	return r
328}
329
330func htmlSmartypants(ob *bytes.Buffer, text []byte, opaque interface{}) {
331	options := opaque.(*htmlOptions)
332	smrt := smartypantsData{false, false}
333
334	// first do normal entity escaping
335	var escaped bytes.Buffer
336	attrEscape(&escaped, text)
337	text = escaped.Bytes()
338
339	mark := 0
340	for i := 0; i < len(text); i++ {
341		if action := options.smartypants[text[i]]; action != nil {
342			if i > mark {
343				ob.Write(text[mark:i])
344			}
345
346			previousChar := byte(0)
347			if i > 0 {
348				previousChar = text[i-1]
349			}
350			i += action(ob, &smrt, previousChar, text[i:])
351			mark = i + 1
352		}
353	}
354
355	if mark < len(text) {
356		ob.Write(text[mark:])
357	}
358}