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("&rsquo;")
   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("&rsquo;")
   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("&copy;")
   159  			return 2
   160  		}
   161  
   162  		if t1 == 'r' && t2 == ')' {
   163  			out.WriteString("&reg;")
   164  			return 2
   165  		}
   166  
   167  		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
   168  			out.WriteString("&trade;")
   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("&mdash;")
   181  			return 1
   182  		}
   183  
   184  		if wordBoundary(previousChar) && wordBoundary(text[1]) {
   185  			out.WriteString("&ndash;")
   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("&mdash;")
   197  		return 2
   198  	}
   199  	if len(text) >= 2 && text[1] == '-' {
   200  		out.WriteString("&ndash;")
   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("&quot;")) {
   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("&#0;")) {
   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("&hellip;")
   238  		return 2
   239  	}
   240  
   241  	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
   242  		out.WriteString("&hellip;")
   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>&frasl;<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("&frac12;")
   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("&frac14;")
   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("&frac34;")
   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("&quot;")
   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  }