emojipy.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. # -*- coding: utf-8; -*-
  2. from __future__ import unicode_literals
  3. import re
  4. import six
  5. if six.PY3:
  6. from html import escape, unescape
  7. else:
  8. from cgi import escape
  9. from HTMLParser import HTMLParser
  10. import struct
  11. unescape = HTMLParser.unescape
  12. chr = unichr
  13. from .ruleset import unicode_replace,\
  14. shortcode_replace, ascii_replace
  15. class Emoji(object):
  16. ascii = False
  17. unicode_alt = True
  18. image_type = 'png'
  19. cache_bust_param = '?v=1.2.5'
  20. sprites = False
  21. image_png_path = 'https://cdn.jsdelivr.net/emojione/assets/png/'
  22. image_svg_path = 'https://cdn.jsdelivr.net/emojione/assets/svg/'
  23. image_path_svg_sprites = './../../assets/sprites/emojione.sprites.svg'
  24. ignored_regexp = '<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>'
  25. unicode_regexp = "(" + '|'.join([re.escape(x.decode("utf-8")) for x in unicode_replace]) + ")"
  26. shortcode_regexp = ':([-+\\w]+):'
  27. ascii_regexp = '(\\<3|&lt;3|\\<\\/3|&lt;\\/3|\\:\'\\)|\\:\'\\-\\)|\\:D|\\:\\-D|\\=D|\\:\\)|\\:\\-\\)|\\=\\]|\\=\\)|\\:\\]|\'\\:\\)|\'\\:\\-\\)|\'\\=\\)|\'\\:D|\'\\:\\-D|\'\\=D|\\>\\:\\)|&gt;\\:\\)|\\>;\\)|&gt;;\\)|\\>\\:\\-\\)|&gt;\\:\\-\\)|\\>\\=\\)|&gt;\\=\\)|;\\)|;\\-\\)|\\*\\-\\)|\\*\\)|;\\-\\]|;\\]|;D|;\\^\\)|\'\\:\\(|\'\\:\\-\\(|\'\\=\\(|\\:\\*|\\:\\-\\*|\\=\\*|\\:\\^\\*|\\>\\:P|&gt;\\:P|X\\-P|x\\-p|\\>\\:\\[|&gt;\\:\\[|\\:\\-\\(|\\:\\(|\\:\\-\\[|\\:\\[|\\=\\(|\\>\\:\\(|&gt;\\:\\(|\\>\\:\\-\\(|&gt;\\:\\-\\(|\\:@|\\:\'\\(|\\:\'\\-\\(|;\\(|;\\-\\(|\\>\\.\\<|&gt;\\.&lt;|\\:\\$|\\=\\$|#\\-\\)|#\\)|%\\-\\)|%\\)|X\\)|X\\-\\)|\\*\\\\0\\/\\*|\\\\0\\/|\\*\\\\O\\/\\*|\\\\O\\/|O\\:\\-\\)|0\\:\\-3|0\\:3|0\\:\\-\\)|0\\:\\)|0;\\^\\)|O\\:\\-\\)|O\\:\\)|O;\\-\\)|O\\=\\)|0;\\-\\)|O\\:\\-3|O\\:3|B\\-\\)|B\\)|8\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\-__\\-|\\-___\\-|\\>\\:\\\\|&gt;\\:\\\\|\\>\\:\\/|&gt;\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\:\\-P|\\=P|\\:\\-p|\\:p|\\=p|\\:\\-Þ|\\:\\-&THORN;|\\:Þ|\\:&THORN;|\\:þ|\\:&thorn;|\\:\\-þ|\\:\\-&thorn;|\\:\\-b|\\:b|d\\:|\\:\\-O|\\:O|\\:\\-o|\\:o|O_O|\\>\\:O|&gt;\\:O|\\:\\-X|\\:X|\\:\\-#|\\:#|\\=X|\\=x|\\:x|\\:\\-x|\\=#)'
  28. shortcode_compiled = re.compile(ignored_regexp+"|("+shortcode_regexp+")",
  29. re.IGNORECASE)
  30. unicode_compiled = re.compile(ignored_regexp+"|("+unicode_regexp+")",
  31. re.UNICODE)
  32. ascii_compiled = re.compile(ignored_regexp+"|("+ascii_regexp+")",
  33. re.IGNORECASE)
  34. @classmethod
  35. def to_image(cls, text):
  36. text = cls.unicode_to_image(text)
  37. text = cls.shortcode_to_image(text)
  38. return text
  39. @classmethod
  40. def unicode_to_image(cls, text):
  41. def replace_unicode(match):
  42. unicode_char = text[match.start():match.end()]
  43. unicode_encoded = unicode_char.encode('utf-8')
  44. if not unicode_encoded or unicode_encoded not in unicode_replace:
  45. return unicode_char # unsupported unicode char
  46. shortcode = unicode_replace[unicode_encoded]
  47. if cls.unicode_alt:
  48. alt = unicode_char
  49. else:
  50. alt = shortcode
  51. filename = shortcode_replace[shortcode]
  52. if cls.image_type == 'png':
  53. if cls.sprites:
  54. return '<span class="emojione emojione-%s" title="%s">%s</span>'\
  55. % (filename, escape(shortcode), alt)
  56. else:
  57. return '<img class="emojione" alt="%s" src="%s"/>' % (
  58. alt,
  59. cls.image_png_path+filename+'.png'+cls.cache_bust_param
  60. )
  61. else:
  62. if cls.sprites:
  63. return '<svg class="emojione"><description>%s</description>\
  64. <use xlink:href="%s#emoji-%s"</use></svg>' % (
  65. alt, cls.image_path_svg_sprites, filename)
  66. else:
  67. return '<object class="emojione" data="%s" \
  68. type="image/svg+xml" standby="%s"> %s</object>' % (
  69. cls.image_svg_path+filename+'.svg'+cls.cache_bust_param, alt, alt)
  70. text = re.sub(cls.unicode_compiled, replace_unicode, text)
  71. return text
  72. @classmethod
  73. def shortcode_to_image(cls, text):
  74. def replace_shortcode(match):
  75. shortcode = text[match.start():match.end()]
  76. if not shortcode or shortcode not in shortcode_replace:
  77. return shortcode
  78. unicode = shortcode_replace[shortcode]
  79. if cls.unicode_alt:
  80. alt = cls.convert(unicode)
  81. else:
  82. alt = shortcode
  83. filename = shortcode_replace[shortcode]
  84. if cls.image_type == 'png':
  85. if cls.sprites:
  86. return '<span class="emojione emojione-%s" title="%s">%s</span>'\
  87. % (filename, escape(shortcode), alt)
  88. else:
  89. return '<img class="emojione" alt="%s" src="%s"/>' % (
  90. alt,
  91. cls.image_png_path+filename+'.png'+cls.cache_bust_param
  92. )
  93. else:
  94. if cls.sprites:
  95. return '<svg class="emojione"><description>%s</description>\
  96. <use xlink:href="%s#emoji-%s"</use></svg>' % (
  97. alt, cls.image_path_svg_sprites, filename)
  98. else:
  99. return '<object class="emojione" data="%s" \
  100. type="image/svg+xml" standby="%s"> %s</object>' % (
  101. cls.image_svg_path+filename+'.svg'+cls.cache_bust_param, alt, alt)
  102. text = re.sub(cls.shortcode_compiled, replace_shortcode, text)
  103. if cls.ascii:
  104. return cls.ascii_to_image(text)
  105. return text
  106. @classmethod
  107. def shortcode_to_ascii(cls, text):
  108. def replace_shortcode(match):
  109. shortcode = text[match.start():match.end()]
  110. if not shortcode or shortcode not in shortcode_replace:
  111. return shortcode
  112. unicode = shortcode_replace[shortcode]
  113. reverse_ascii_unicode = {v: k for k, v in ascii_replace.items()}
  114. if unicode in reverse_ascii_unicode:
  115. return reverse_ascii_unicode[unicode]
  116. return shortcode
  117. return re.sub(cls.shortcode_compiled, replace_shortcode, text)
  118. @classmethod
  119. def shortcode_to_unicode(cls, text):
  120. def replace_shortcode(match):
  121. shortcode = text[match.start():match.end()]
  122. if not shortcode or shortcode not in shortcode_replace:
  123. return shortcode
  124. flipped_unicode_replace = {v: k for k, v in unicode_replace.items()}
  125. if shortcode in flipped_unicode_replace:
  126. return flipped_unicode_replace[shortcode].decode('utf8')
  127. return shortcode
  128. text = re.sub(cls.shortcode_compiled, replace_shortcode, text)
  129. if cls.ascii:
  130. return cls.ascii_to_unicode(text)
  131. return text
  132. @classmethod
  133. def ascii_to_unicode(cls, text):
  134. def replace_ascii(match):
  135. ascii = text[match.start():match.end()]
  136. ascii = unescape(ascii) # convert escaped HTML entities back to original chars
  137. if not ascii or ascii not in ascii_replace:
  138. return ascii
  139. return cls.convert(ascii_replace[ascii])
  140. return re.sub(cls.ascii_compiled, replace_ascii, text)
  141. @classmethod
  142. def ascii_to_image(cls, text):
  143. def replace_ascii(match):
  144. ascii = text[match.start():match.end()]
  145. ascii = unescape(ascii) # convert escaped HTML entities back to original chars
  146. if not ascii or ascii not in ascii_replace:
  147. return ascii
  148. unicode = ascii_replace[ascii]
  149. if cls.unicode_alt:
  150. alt = cls.convert(unicode)
  151. else:
  152. alt = escape(ascii)
  153. if cls.image_type == 'png':
  154. if cls.sprites:
  155. return '<span class="emojione emojione-%s" title="%s">%s</span>'\
  156. % (unicode, escape(ascii), alt)
  157. else:
  158. return '<img class="emojione" alt="%s" src="%s"/>' % (
  159. alt,
  160. cls.image_png_path+unicode+'.png'+cls.cache_bust_param
  161. )
  162. else:
  163. if cls.sprites:
  164. return '<svg class="emojione"><description>%s</description>\
  165. <use xlink:href="%s#emoji-%s"</use></svg>' % (
  166. alt, cls.image_path_svg_sprites, unicode)
  167. else:
  168. return '<object class="emojione" data="%s" \
  169. type="image/svg+xml" standby="%s"> %s</object>' % (
  170. cls.image_svg_path+unicode+'.svg'+cls.cache_bust_param,
  171. alt, alt)
  172. return re.sub(cls.ascii_compiled, replace_ascii, text)
  173. @classmethod
  174. def convert(cls, hex_unicode):
  175. def char(i):
  176. try:
  177. return chr(i)
  178. except ValueError:
  179. return struct.pack('i', i).decode('utf-32')
  180. """
  181. Convert a unicode in hex string to actual unicode char
  182. """
  183. if '-' not in hex_unicode:
  184. return char(int(hex_unicode, 16))
  185. parts = hex_unicode.split('-')
  186. return ''.join(char(int(x, 16)) for x in parts)