toktok.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # -*- coding: utf-8 -*-
  2. # Natural Language Toolkit: Python port of the tok-tok.pl tokenizer.
  3. #
  4. # Copyright (C) 2001-2015 NLTK Project
  5. # Author: Jon Dehdari
  6. # Contributors: Liling Tan, Selcuk Ayguney, ikegami, Martijn Pieters
  7. #
  8. # URL: <http://nltk.sourceforge.net>
  9. # For license information, see LICENSE.TXT
  10. """
  11. The tok-tok tokenizer is a simple, general tokenizer, where the input has one
  12. sentence per line; thus only final period is tokenized.
  13. Tok-tok has been tested on, and gives reasonably good results for English,
  14. Persian, Russian, Czech, French, German, Vietnamese, Tajik, and a few others.
  15. The input should be in UTF-8 encoding.
  16. Reference:
  17. Jon Dehdari. 2014. A Neurophysiologically-Inspired Statistical Language
  18. Model (Doctoral dissertation). Columbus, OH, USA: The Ohio State University.
  19. """
  20. import re
  21. from nltk.tokenize.api import TokenizerI
  22. class ToktokTokenizer(TokenizerI):
  23. """
  24. This is a Python port of the tok-tok.pl from
  25. https://github.com/jonsafari/tok-tok/blob/master/tok-tok.pl
  26. >>> toktok = ToktokTokenizer()
  27. >>> text = u'Is 9.5 or 525,600 my favorite number?'
  28. >>> print(toktok.tokenize(text, return_str=True))
  29. Is 9.5 or 525,600 my favorite number ?
  30. >>> text = u'The https://github.com/jonsafari/tok-tok/blob/master/tok-tok.pl is a website with/and/or slashes and sort of weird : things'
  31. >>> print(toktok.tokenize(text, return_str=True))
  32. The https://github.com/jonsafari/tok-tok/blob/master/tok-tok.pl is a website with/and/or slashes and sort of weird : things
  33. >>> text = u'\xa1This, is a sentence with weird\xbb symbols\u2026 appearing everywhere\xbf'
  34. >>> expected = u'\xa1 This , is a sentence with weird \xbb symbols \u2026 appearing everywhere \xbf'
  35. >>> assert toktok.tokenize(text, return_str=True) == expected
  36. >>> toktok.tokenize(text) == [u'\xa1', u'This', u',', u'is', u'a', u'sentence', u'with', u'weird', u'\xbb', u'symbols', u'\u2026', u'appearing', u'everywhere', u'\xbf']
  37. True
  38. """
  39. # Replace non-breaking spaces with normal spaces.
  40. NON_BREAKING = re.compile(u"\u00A0"), " "
  41. # Pad some funky punctuation.
  42. FUNKY_PUNCT_1 = re.compile(u'([،;؛¿!"\])}»›”؟¡%٪°±©®।॥…])'), r" \1 "
  43. # Pad more funky punctuation.
  44. FUNKY_PUNCT_2 = re.compile(u"([({\[“‘„‚«‹「『])"), r" \1 "
  45. # Pad En dash and em dash
  46. EN_EM_DASHES = re.compile(u"([–—])"), r" \1 "
  47. # Replace problematic character with numeric character reference.
  48. AMPERCENT = re.compile("& "), "&amp; "
  49. TAB = re.compile("\t"), " &#9; "
  50. PIPE = re.compile("\|"), " &#124; "
  51. # Pad numbers with commas to keep them from further tokenization.
  52. COMMA_IN_NUM = re.compile(r"(?<!,)([,،])(?![,\d])"), r" \1 "
  53. # Just pad problematic (often neurotic) hyphen/single quote, etc.
  54. PROB_SINGLE_QUOTES = re.compile(r"(['’`])"), r" \1 "
  55. # Group ` ` stupid quotes ' ' into a single token.
  56. STUPID_QUOTES_1 = re.compile(r" ` ` "), r" `` "
  57. STUPID_QUOTES_2 = re.compile(r" ' ' "), r" '' "
  58. # Don't tokenize period unless it ends the line and that it isn't
  59. # preceded by another period, e.g.
  60. # "something ..." -> "something ..."
  61. # "something." -> "something ."
  62. FINAL_PERIOD_1 = re.compile(r"(?<!\.)\.$"), r" ."
  63. # Don't tokenize period unless it ends the line eg.
  64. # " ... stuff." -> "... stuff ."
  65. FINAL_PERIOD_2 = re.compile(r"""(?<!\.)\.\s*(["'’»›”]) *$"""), r" . \1"
  66. # Treat continuous commas as fake German,Czech, etc.: „
  67. MULTI_COMMAS = re.compile(r"(,{2,})"), r" \1 "
  68. # Treat continuous dashes as fake en-dash, etc.
  69. MULTI_DASHES = re.compile(r"(-{2,})"), r" \1 "
  70. # Treat multiple periods as a thing (eg. ellipsis)
  71. MULTI_DOTS = re.compile(r"(\.{2,})"), r" \1 "
  72. # This is the \p{Open_Punctuation} from Perl's perluniprops
  73. # see http://perldoc.perl.org/perluniprops.html
  74. OPEN_PUNCT = str(
  75. u"([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d"
  76. u"\u208d\u2329\u2768\u276a\u276c\u276e\u2770\u2772"
  77. u"\u2774\u27c5\u27e6\u27e8\u27ea\u27ec\u27ee\u2983"
  78. u"\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993"
  79. u"\u2995\u2997\u29d8\u29da\u29fc\u2e22\u2e24\u2e26"
  80. u"\u2e28\u3008\u300a\u300c\u300e\u3010\u3014\u3016"
  81. u"\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39"
  82. u"\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b"
  83. u"\ufe5d\uff08\uff3b\uff5b\uff5f\uff62"
  84. )
  85. # This is the \p{Close_Punctuation} from Perl's perluniprops
  86. CLOSE_PUNCT = str(
  87. u")]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a"
  88. u"\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6"
  89. u"\u27e7\u27e9\u27eb\u27ed\u27ef\u2984\u2986\u2988"
  90. u"\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998"
  91. u"\u29d9\u29db\u29fd\u2e23\u2e25\u2e27\u2e29\u3009"
  92. u"\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b"
  93. u"\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c"
  94. u"\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e"
  95. u"\uff09\uff3d\uff5d\uff60\uff63"
  96. )
  97. # This is the \p{Close_Punctuation} from Perl's perluniprops
  98. CURRENCY_SYM = str(
  99. u"$\xa2\xa3\xa4\xa5\u058f\u060b\u09f2\u09f3\u09fb"
  100. u"\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3"
  101. u"\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab"
  102. u"\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3"
  103. u"\u20b4\u20b5\u20b6\u20b7\u20b8\u20b9\u20ba\ua838"
  104. u"\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6"
  105. )
  106. # Pad spaces after opening punctuations.
  107. OPEN_PUNCT_RE = re.compile(u"([{}])".format(OPEN_PUNCT)), r"\1 "
  108. # Pad spaces before closing punctuations.
  109. CLOSE_PUNCT_RE = re.compile(u"([{}])".format(CLOSE_PUNCT)), r"\1 "
  110. # Pad spaces after currency symbols.
  111. CURRENCY_SYM_RE = re.compile(u"([{}])".format(CURRENCY_SYM)), r"\1 "
  112. # Use for tokenizing URL-unfriendly characters: [:/?#]
  113. URL_FOE_1 = re.compile(r":(?!//)"), r" : " # in perl s{:(?!//)}{ : }g;
  114. URL_FOE_2 = re.compile(r"\?(?!\S)"), r" ? " # in perl s{\?(?!\S)}{ ? }g;
  115. # in perl: m{://} or m{\S+\.\S+/\S+} or s{/}{ / }g;
  116. URL_FOE_3 = re.compile(r"(:\/\/)[\S+\.\S+\/\S+][\/]"), " / "
  117. URL_FOE_4 = re.compile(r" /"), r" / " # s{ /}{ / }g;
  118. # Left/Right strip, i.e. remove heading/trailing spaces.
  119. # These strip regexes should NOT be used,
  120. # instead use str.lstrip(), str.rstrip() or str.strip()
  121. # (They are kept for reference purposes to the original toktok.pl code)
  122. LSTRIP = re.compile(r"^ +"), ""
  123. RSTRIP = re.compile(r"\s+$"), "\n"
  124. # Merge multiple spaces.
  125. ONE_SPACE = re.compile(r" {2,}"), " "
  126. TOKTOK_REGEXES = [
  127. NON_BREAKING,
  128. FUNKY_PUNCT_1,
  129. URL_FOE_1,
  130. URL_FOE_2,
  131. URL_FOE_3,
  132. URL_FOE_4,
  133. AMPERCENT,
  134. TAB,
  135. PIPE,
  136. OPEN_PUNCT_RE,
  137. CLOSE_PUNCT_RE,
  138. MULTI_COMMAS,
  139. COMMA_IN_NUM,
  140. FINAL_PERIOD_2,
  141. PROB_SINGLE_QUOTES,
  142. STUPID_QUOTES_1,
  143. STUPID_QUOTES_2,
  144. CURRENCY_SYM_RE,
  145. EN_EM_DASHES,
  146. MULTI_DASHES,
  147. MULTI_DOTS,
  148. FINAL_PERIOD_1,
  149. FINAL_PERIOD_2,
  150. ONE_SPACE,
  151. ]
  152. def tokenize(self, text, return_str=False):
  153. text = str(text) # Converts input string into unicode.
  154. for regexp, subsitution in self.TOKTOK_REGEXES:
  155. text = regexp.sub(subsitution, text)
  156. # Finally, strips heading and trailing spaces
  157. # and converts output string into unicode.
  158. text = str(text.strip())
  159. return text if return_str else text.split()