github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/russross/blackfriday/smartypants.go (about) 1 // 2 // Blackfriday Markdown Processor 3 // Available at http://yougam/libraries/russross/blackfriday 4 // 5 // Copyright © 2011 Russ Ross <russ@russross.com>. 6 // Distributed under the Simplified BSD License. 7 // See README.md for details. 8 // 9 10 // 11 // 12 // SmartyPants rendering 13 // 14 // 15 16 package blackfriday 17 18 import ( 19 "bytes" 20 ) 21 22 type smartypantsData struct { 23 inSingleQuote bool 24 inDoubleQuote bool 25 } 26 27 func wordBoundary(c byte) bool { 28 return c == 0 || isspace(c) || ispunct(c) 29 } 30 31 func tolower(c byte) byte { 32 if c >= 'A' && c <= 'Z' { 33 return c - 'A' + 'a' 34 } 35 return c 36 } 37 38 func isdigit(c byte) bool { 39 return c >= '0' && c <= '9' 40 } 41 42 func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool { 43 // edge of the buffer is likely to be a tag that we don't get to see, 44 // so we treat it like text sometimes 45 46 // enumerate all sixteen possibilities for (previousChar, nextChar) 47 // each can be one of {0, space, punct, other} 48 switch { 49 case previousChar == 0 && nextChar == 0: 50 // context is not any help here, so toggle 51 *isOpen = !*isOpen 52 case isspace(previousChar) && nextChar == 0: 53 // [ "] might be [ "<code>foo...] 54 *isOpen = true 55 case ispunct(previousChar) && nextChar == 0: 56 // [!"] hmm... could be [Run!"] or [("<code>...] 57 *isOpen = false 58 case /* isnormal(previousChar) && */ nextChar == 0: 59 // [a"] is probably a close 60 *isOpen = false 61 case previousChar == 0 && isspace(nextChar): 62 // [" ] might be [...foo</code>" ] 63 *isOpen = false 64 case isspace(previousChar) && isspace(nextChar): 65 // [ " ] context is not any help here, so toggle 66 *isOpen = !*isOpen 67 case ispunct(previousChar) && isspace(nextChar): 68 // [!" ] is probably a close 69 *isOpen = false 70 case /* isnormal(previousChar) && */ isspace(nextChar): 71 // [a" ] this is one of the easy cases 72 *isOpen = false 73 case previousChar == 0 && ispunct(nextChar): 74 // ["!] hmm... could be ["$1.95] or [</code>"!...] 75 *isOpen = false 76 case isspace(previousChar) && ispunct(nextChar): 77 // [ "!] looks more like [ "$1.95] 78 *isOpen = true 79 case ispunct(previousChar) && ispunct(nextChar): 80 // [!"!] context is not any help here, so toggle 81 *isOpen = !*isOpen 82 case /* isnormal(previousChar) && */ ispunct(nextChar): 83 // [a"!] is probably a close 84 *isOpen = false 85 case previousChar == 0 /* && isnormal(nextChar) */ : 86 // ["a] is probably an open 87 *isOpen = true 88 case isspace(previousChar) /* && isnormal(nextChar) */ : 89 // [ "a] this is one of the easy cases 90 *isOpen = true 91 case ispunct(previousChar) /* && isnormal(nextChar) */ : 92 // [!"a] is probably an open 93 *isOpen = true 94 default: 95 // [a'b] maybe a contraction? 96 *isOpen = false 97 } 98 99 out.WriteByte('&') 100 if *isOpen { 101 out.WriteByte('l') 102 } else { 103 out.WriteByte('r') 104 } 105 out.WriteByte(quote) 106 out.WriteString("quo;") 107 return true 108 } 109 110 func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 111 if len(text) >= 2 { 112 t1 := tolower(text[1]) 113 114 if t1 == '\'' { 115 nextChar := byte(0) 116 if len(text) >= 3 { 117 nextChar = text[2] 118 } 119 if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { 120 return 1 121 } 122 } 123 124 if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) { 125 out.WriteString("’") 126 return 0 127 } 128 129 if len(text) >= 3 { 130 t2 := tolower(text[2]) 131 132 if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && 133 (len(text) < 4 || wordBoundary(text[3])) { 134 out.WriteString("’") 135 return 0 136 } 137 } 138 } 139 140 nextChar := byte(0) 141 if len(text) > 1 { 142 nextChar = text[1] 143 } 144 if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) { 145 return 0 146 } 147 148 out.WriteByte(text[0]) 149 return 0 150 } 151 152 func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 153 if len(text) >= 3 { 154 t1 := tolower(text[1]) 155 t2 := tolower(text[2]) 156 157 if t1 == 'c' && t2 == ')' { 158 out.WriteString("©") 159 return 2 160 } 161 162 if t1 == 'r' && t2 == ')' { 163 out.WriteString("®") 164 return 2 165 } 166 167 if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { 168 out.WriteString("™") 169 return 3 170 } 171 } 172 173 out.WriteByte(text[0]) 174 return 0 175 } 176 177 func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 178 if len(text) >= 2 { 179 if text[1] == '-' { 180 out.WriteString("—") 181 return 1 182 } 183 184 if wordBoundary(previousChar) && wordBoundary(text[1]) { 185 out.WriteString("–") 186 return 0 187 } 188 } 189 190 out.WriteByte(text[0]) 191 return 0 192 } 193 194 func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 195 if len(text) >= 3 && text[1] == '-' && text[2] == '-' { 196 out.WriteString("—") 197 return 2 198 } 199 if len(text) >= 2 && text[1] == '-' { 200 out.WriteString("–") 201 return 1 202 } 203 204 out.WriteByte(text[0]) 205 return 0 206 } 207 208 func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { 209 if bytes.HasPrefix(text, []byte(""")) { 210 nextChar := byte(0) 211 if len(text) >= 7 { 212 nextChar = text[6] 213 } 214 if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { 215 return 5 216 } 217 } 218 219 if bytes.HasPrefix(text, []byte("�")) { 220 return 3 221 } 222 223 out.WriteByte('&') 224 return 0 225 } 226 227 func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 228 return smartAmpVariant(out, smrt, previousChar, text, 'd') 229 } 230 231 func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 232 return smartAmpVariant(out, smrt, previousChar, text, 'a') 233 } 234 235 func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 236 if len(text) >= 3 && text[1] == '.' && text[2] == '.' { 237 out.WriteString("…") 238 return 2 239 } 240 241 if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { 242 out.WriteString("…") 243 return 4 244 } 245 246 out.WriteByte(text[0]) 247 return 0 248 } 249 250 func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 251 if len(text) >= 2 && text[1] == '`' { 252 nextChar := byte(0) 253 if len(text) >= 3 { 254 nextChar = text[2] 255 } 256 if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { 257 return 1 258 } 259 } 260 261 out.WriteByte(text[0]) 262 return 0 263 } 264 265 func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 266 if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { 267 // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b 268 // note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8) 269 // and avoid changing dates like 1/23/2005 into fractions. 270 numEnd := 0 271 for len(text) > numEnd && isdigit(text[numEnd]) { 272 numEnd++ 273 } 274 if numEnd == 0 { 275 out.WriteByte(text[0]) 276 return 0 277 } 278 denStart := numEnd + 1 279 if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 { 280 denStart = numEnd + 3 281 } else if len(text) < numEnd+2 || text[numEnd] != '/' { 282 out.WriteByte(text[0]) 283 return 0 284 } 285 denEnd := denStart 286 for len(text) > denEnd && isdigit(text[denEnd]) { 287 denEnd++ 288 } 289 if denEnd == denStart { 290 out.WriteByte(text[0]) 291 return 0 292 } 293 if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' { 294 out.WriteString("<sup>") 295 out.Write(text[:numEnd]) 296 out.WriteString("</sup>⁄<sub>") 297 out.Write(text[denStart:denEnd]) 298 out.WriteString("</sub>") 299 return denEnd - 1 300 } 301 } 302 303 out.WriteByte(text[0]) 304 return 0 305 } 306 307 func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 308 if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { 309 if text[0] == '1' && text[1] == '/' && text[2] == '2' { 310 if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { 311 out.WriteString("½") 312 return 2 313 } 314 } 315 316 if text[0] == '1' && text[1] == '/' && text[2] == '4' { 317 if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') { 318 out.WriteString("¼") 319 return 2 320 } 321 } 322 323 if text[0] == '3' && text[1] == '/' && text[2] == '4' { 324 if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') { 325 out.WriteString("¾") 326 return 2 327 } 328 } 329 } 330 331 out.WriteByte(text[0]) 332 return 0 333 } 334 335 func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { 336 nextChar := byte(0) 337 if len(text) > 1 { 338 nextChar = text[1] 339 } 340 if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { 341 out.WriteString(""") 342 } 343 344 return 0 345 } 346 347 func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 348 return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd') 349 } 350 351 func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 352 return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a') 353 } 354 355 func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { 356 i := 0 357 358 for i < len(text) && text[i] != '>' { 359 i++ 360 } 361 362 out.Write(text[:i+1]) 363 return i 364 } 365 366 type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int 367 368 type smartypantsRenderer [256]smartCallback 369 370 func smartypants(flags int) *smartypantsRenderer { 371 r := new(smartypantsRenderer) 372 if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 { 373 r['"'] = smartDoubleQuote 374 r['&'] = smartAmp 375 } else { 376 r['"'] = smartAngledDoubleQuote 377 r['&'] = smartAmpAngledQuote 378 } 379 r['\''] = smartSingleQuote 380 r['('] = smartParens 381 if flags&HTML_SMARTYPANTS_DASHES != 0 { 382 if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 { 383 r['-'] = smartDash 384 } else { 385 r['-'] = smartDashLatex 386 } 387 } 388 r['.'] = smartPeriod 389 if flags&HTML_SMARTYPANTS_FRACTIONS == 0 { 390 r['1'] = smartNumber 391 r['3'] = smartNumber 392 } else { 393 for ch := '1'; ch <= '9'; ch++ { 394 r[ch] = smartNumberGeneric 395 } 396 } 397 r['<'] = smartLeftAngle 398 r['`'] = smartBacktick 399 return r 400 }