nltk 3.2__zip → 3.3__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
  2. {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
  3. {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
  4. {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
  5. nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
  6. nltk-3.3/nltk/VERSION +1 -0
  7. {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
  8. {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
  9. {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
  10. {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
  11. {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
  12. {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
  13. {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
  14. {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
  15. {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
  16. {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
  17. {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
  18. {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
  19. {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
  20. {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
  21. {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
  22. {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
  23. {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
  24. nltk-3.3/nltk/ccg/logic.py +46 -0
  25. {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
  26. {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
  27. {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
  28. {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
  29. {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
  30. {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
  31. {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
  32. {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
  33. {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
  34. {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
  35. {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
  36. {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
  37. {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
  38. {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
  39. {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
  40. {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
  41. {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
  42. {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
  43. {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
  44. {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
  45. {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
  46. {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
  47. {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
  48. {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
  49. {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
  50. {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
  51. {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
  52. {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
  53. {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
  54. {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
  55. {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
  56. {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
  57. {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
  58. nltk-3.3/nltk/collections.py +640 -0
  59. {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
  60. nltk-3.3/nltk/compat.py +365 -0
  61. {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
  62. {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
  63. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
  64. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
  65. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
  66. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
  67. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
  68. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
  69. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
  70. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
  71. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
  72. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
  73. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
  74. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
  75. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
  76. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
  77. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
  78. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
  79. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
  80. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
  81. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
  82. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
  83. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
  84. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
  85. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
  86. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
  87. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
  88. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
  89. nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
  90. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
  91. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
  92. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
  93. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
  94. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
  95. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
  96. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
  97. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
  98. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
  99. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
  100. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
  101. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
  102. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
  103. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
  104. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
  105. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
  106. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
  107. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
  108. nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
  109. nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
  110. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
  111. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
  112. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
  113. {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
  114. {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
  115. {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
  116. {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
  117. {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
  118. {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
  119. {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
  120. {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
  121. {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
  122. {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
  123. {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
  124. {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
  125. {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
  126. {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
  127. {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
  128. {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
  129. {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
  130. {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
  131. {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
  132. {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
  133. {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
  134. {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
  135. {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
  136. {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
  137. {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
  138. {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
  139. nltk-3.3/nltk/metrics/aline.py +607 -0
  140. {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
  141. {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
  142. {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
  143. {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
  144. {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
  145. {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
  146. {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
  147. {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
  148. {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
  149. {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
  150. {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
  151. {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
  152. {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
  153. {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
  154. {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
  155. {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
  156. {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
  157. nltk-3.3/nltk/parse/corenlp.py +780 -0
  158. {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
  159. {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
  160. {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
  161. {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
  162. {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
  163. {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
  164. {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
  165. {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
  166. {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
  167. {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
  168. {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
  169. {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
  170. {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
  171. {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
  172. {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
  173. {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
  174. {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
  175. {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
  176. {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
  177. {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
  178. {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
  179. {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
  180. {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
  181. {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
  182. {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
  183. {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
  184. {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
  185. {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
  186. {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
  187. {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
  188. {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
  189. {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
  190. {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
  191. {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
  192. {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
  193. {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
  194. {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
  195. nltk-3.3/nltk/stem/arlstem.py +369 -0
  196. {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
  197. {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
  198. nltk-3.3/nltk/stem/porter.py +710 -0
  199. {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
  200. {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
  201. {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
  202. {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
  203. {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
  204. {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
  205. {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
  206. {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
  207. {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
  208. {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
  209. {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
  210. {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
  211. {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
  212. {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
  213. {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
  214. {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
  215. {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
  216. {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
  217. {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
  218. {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
  219. {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
  220. {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
  221. {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
  222. {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
  223. {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
  224. {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
  225. {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
  226. {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
  227. {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
  228. {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
  229. {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
  230. nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
  231. {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
  232. {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
  233. {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
  234. {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
  235. {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
  236. {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
  237. nltk-3.3/nltk/test/collections.doctest +20 -0
  238. {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
  239. {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
  240. {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
  241. nltk-3.3/nltk/test/concordance.doctest +55 -0
  242. {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
  243. {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
  244. {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
  245. {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
  246. {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
  247. {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
  248. {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
  249. {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
  250. {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
  251. {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
  252. {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
  253. {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
  254. {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
  255. {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
  256. {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
  257. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
  258. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
  259. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
  260. {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
  261. {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
  262. {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
  263. {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
  264. {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
  265. {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
  266. {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
  267. {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
  268. {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
  269. {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
  270. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
  271. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
  272. {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
  273. {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
  274. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
  275. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
  276. {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
  277. {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
  278. {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
  279. {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
  280. {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
  281. {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
  282. {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
  283. {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
  284. {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
  285. {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
  286. {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
  287. {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
  288. {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
  289. {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
  290. {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
  291. {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
  292. {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
  293. {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
  294. {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
  295. {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
  296. {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
  297. nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
  298. nltk-3.3/nltk/test/unit/test_aline.py +52 -0
  299. nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
  300. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
  301. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
  302. nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
  303. nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
  304. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
  305. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
  306. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
  307. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
  308. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
  309. nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
  310. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
  311. nltk-3.3/nltk/test/unit/test_senna.py +74 -0
  312. nltk-3.3/nltk/test/unit/test_stem.py +121 -0
  313. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
  314. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
  315. nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
  316. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
  317. nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
  318. nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
  319. nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
  320. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
  321. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
  322. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
  323. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
  324. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
  325. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
  326. nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
  327. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
  328. {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
  329. {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
  330. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
  331. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
  332. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
  333. {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
  334. {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
  335. {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
  336. {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
  337. {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
  338. {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
  339. {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
  340. nltk-3.3/nltk/tokenize/nist.py +173 -0
  341. {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
  342. {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
  343. nltk-3.3/nltk/tokenize/repp.py +151 -0
  344. {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
  345. {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
  346. {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
  347. nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
  348. {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
  349. nltk-3.3/nltk/tokenize/toktok.py +155 -0
  350. nltk-3.3/nltk/tokenize/treebank.py +363 -0
  351. nltk-3.3/nltk/tokenize/util.py +261 -0
  352. {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
  353. {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
  354. {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
  355. {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
  356. nltk-3.3/nltk/translate/chrf_score.py +137 -0
  357. {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
  358. {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
  359. nltk-3.3/nltk/translate/gleu_score.py +193 -0
  360. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
  361. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
  362. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
  363. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
  364. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
  365. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
  366. {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
  367. nltk-3.3/nltk/translate/nist_score.py +178 -0
  368. {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
  369. {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
  370. {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
  371. {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
  372. {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
  373. {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
  374. {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
  375. {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
  376. {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
  377. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
  378. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
  379. {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
  380. nltk-3.3/nltk/util.py +751 -0
  381. {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
  382. {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
  383. {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
  384. nltk-3.3/nltk.egg-info/requires.txt +31 -0
  385. {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
  386. {nltk-3.2 → nltk-3.3}/setup.py +102 -75
  387. nltk-3.2/nltk/VERSION +0 -1
  388. nltk-3.2/nltk/compat.py +0 -680
  389. nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
  390. nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
  391. nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
  392. nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
  393. nltk-3.2/nltk/six.py +0 -868
  394. nltk-3.2/nltk/stem/porter.py +0 -692
  395. nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
  396. nltk-3.2/nltk/test/unit/test_stem.py +0 -36
  397. nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
  398. nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
  399. nltk-3.2/nltk/tokenize/treebank.py +0 -124
  400. nltk-3.2/nltk/tokenize/util.py +0 -90
  401. nltk-3.2/nltk/util.py +0 -1364
  402. {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
  403. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
  404. {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
  405. {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
  406. {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
@@ -1,1384 +1,1390 @@
1
- # Natural Language Toolkit: Regular Expression Chunkers
2
- #
3
- # Copyright (C) 2001-2015 NLTK Project
4
- # Author: Edward Loper <edloper@gmail.com>
5
- # Steven Bird <stevenbird1@gmail.com> (minor additions)
6
- # URL: <http://nltk.org/>
7
- # For license information, see LICENSE.TXT
8
- from __future__ import print_function, unicode_literals
9
- from __future__ import division
10
-
11
- import re
12
-
13
- from nltk.tree import Tree
14
- from nltk.chunk.api import ChunkParserI
15
- from nltk.compat import python_2_unicode_compatible, string_types, unicode_repr
16
-
17
- ##//////////////////////////////////////////////////////
18
- ## ChunkString
19
- ##//////////////////////////////////////////////////////
20
-
21
- @python_2_unicode_compatible
22
- class ChunkString(object):
23
- """
24
- A string-based encoding of a particular chunking of a text.
25
- Internally, the ``ChunkString`` class uses a single string to
26
- encode the chunking of the input text. This string contains a
27
- sequence of angle-bracket delimited tags, with chunking indicated
28
- by braces. An example of this encoding is::
29
-
30
- {<DT><JJ><NN>}<VBN><IN>{<DT><NN>}<.>{<DT><NN>}<VBD><.>
31
-
32
- ``ChunkString`` are created from tagged texts (i.e., lists of
33
- ``tokens`` whose type is ``TaggedType``). Initially, nothing is
34
- chunked.
35
-
36
- The chunking of a ``ChunkString`` can be modified with the ``xform()``
37
- method, which uses a regular expression to transform the string
38
- representation. These transformations should only add and remove
39
- braces; they should *not* modify the sequence of angle-bracket
40
- delimited tags.
41
-
42
- :type _str: str
43
- :ivar _str: The internal string representation of the text's
44
- encoding. This string representation contains a sequence of
45
- angle-bracket delimited tags, with chunking indicated by
46
- braces. An example of this encoding is::
47
-
48
- {<DT><JJ><NN>}<VBN><IN>{<DT><NN>}<.>{<DT><NN>}<VBD><.>
49
-
50
- :type _pieces: list(tagged tokens and chunks)
51
- :ivar _pieces: The tagged tokens and chunks encoded by this ``ChunkString``.
52
- :ivar _debug: The debug level. See the constructor docs.
53
-
54
- :cvar IN_CHUNK_PATTERN: A zero-width regexp pattern string that
55
- will only match positions that are in chunks.
56
- :cvar IN_CHINK_PATTERN: A zero-width regexp pattern string that
57
- will only match positions that are in chinks.
58
- """
59
- CHUNK_TAG_CHAR = r'[^\{\}<>]'
60
- CHUNK_TAG = r'(<%s+?>)' % CHUNK_TAG_CHAR
61
-
62
- IN_CHUNK_PATTERN = r'(?=[^\{]*\})'
63
- IN_CHINK_PATTERN = r'(?=[^\}]*(\{|$))'
64
-
65
- # These are used by _verify
66
- _CHUNK = r'(\{%s+?\})+?' % CHUNK_TAG
67
- _CHINK = r'(%s+?)+?' % CHUNK_TAG
68
- _VALID = re.compile(r'^(\{?%s\}?)*?$' % CHUNK_TAG)
69
- _BRACKETS = re.compile('[^\{\}]+')
70
- _BALANCED_BRACKETS = re.compile(r'(\{\})*$')
71
-
72
- def __init__(self, chunk_struct, debug_level=1):
73
- """
74
- Construct a new ``ChunkString`` that encodes the chunking of
75
- the text ``tagged_tokens``.
76
-
77
- :type chunk_struct: Tree
78
- :param chunk_struct: The chunk structure to be further chunked.
79
- :type debug_level: int
80
- :param debug_level: The level of debugging which should be
81
- applied to transformations on the ``ChunkString``. The
82
- valid levels are:
83
- - 0: no checks
84
- - 1: full check on to_chunkstruct
85
- - 2: full check on to_chunkstruct and cursory check after
86
- each transformation.
87
- - 3: full check on to_chunkstruct and full check after
88
- each transformation.
89
- We recommend you use at least level 1. You should
90
- probably use level 3 if you use any non-standard
91
- subclasses of ``RegexpChunkRule``.
92
- """
93
- self._root_label = chunk_struct.label()
94
- self._pieces = chunk_struct[:]
95
- tags = [self._tag(tok) for tok in self._pieces]
96
- self._str = '<' + '><'.join(tags) + '>'
97
- self._debug = debug_level
98
-
99
- def _tag(self, tok):
100
- if isinstance(tok, tuple):
101
- return tok[1]
102
- elif isinstance(tok, Tree):
103
- return tok.label()
104
- else:
105
- raise ValueError('chunk structures must contain tagged '
106
- 'tokens or trees')
107
-
108
- def _verify(self, s, verify_tags):
109
- """
110
- Check to make sure that ``s`` still corresponds to some chunked
111
- version of ``_pieces``.
112
-
113
- :type verify_tags: bool
114
- :param verify_tags: Whether the individual tags should be
115
- checked. If this is false, ``_verify`` will check to make
116
- sure that ``_str`` encodes a chunked version of *some*
117
- list of tokens. If this is true, then ``_verify`` will
118
- check to make sure that the tags in ``_str`` match those in
119
- ``_pieces``.
120
-
121
- :raise ValueError: if the internal string representation of
122
- this ``ChunkString`` is invalid or not consistent with _pieces.
123
- """
124
- # Check overall form
125
- if not ChunkString._VALID.match(s):
126
- raise ValueError('Transformation generated invalid '
127
- 'chunkstring:\n %s' % s)
128
-
129
- # Check that parens are balanced. If the string is long, we
130
- # have to do this in pieces, to avoid a maximum recursion
131
- # depth limit for regular expressions.
132
- brackets = ChunkString._BRACKETS.sub('', s)
133
- for i in range(1 + len(brackets) // 5000):
134
- substr = brackets[i*5000:i*5000+5000]
135
- if not ChunkString._BALANCED_BRACKETS.match(substr):
136
- raise ValueError('Transformation generated invalid '
137
- 'chunkstring:\n %s' % s)
138
-
139
- if verify_tags<=0: return
140
-
141
- tags1 = (re.split(r'[\{\}<>]+', s))[1:-1]
142
- tags2 = [self._tag(piece) for piece in self._pieces]
143
- if tags1 != tags2:
144
- raise ValueError('Transformation generated invalid '
145
- 'chunkstring: tag changed')
146
-
147
- def to_chunkstruct(self, chunk_label='CHUNK'):
148
- """
149
- Return the chunk structure encoded by this ``ChunkString``.
150
-
151
- :rtype: Tree
152
- :raise ValueError: If a transformation has generated an
153
- invalid chunkstring.
154
- """
155
- if self._debug > 0: self._verify(self._str, 1)
156
-
157
- # Use this alternating list to create the chunkstruct.
158
- pieces = []
159
- index = 0
160
- piece_in_chunk = 0
161
- for piece in re.split('[{}]', self._str):
162
-
163
- # Find the list of tokens contained in this piece.
164
- length = piece.count('<')
165
- subsequence = self._pieces[index:index+length]
166
-
167
- # Add this list of tokens to our pieces.
168
- if piece_in_chunk:
169
- pieces.append(Tree(chunk_label, subsequence))
170
- else:
171
- pieces += subsequence
172
-
173
- # Update index, piece_in_chunk
174
- index += length
175
- piece_in_chunk = not piece_in_chunk
176
-
177
- return Tree(self._root_label, pieces)
178
-
179
- def xform(self, regexp, repl):
180
- """
181
- Apply the given transformation to the string encoding of this
182
- ``ChunkString``. In particular, find all occurrences that match
183
- ``regexp``, and replace them using ``repl`` (as done by
184
- ``re.sub``).
185
-
186
- This transformation should only add and remove braces; it
187
- should *not* modify the sequence of angle-bracket delimited
188
- tags. Furthermore, this transformation may not result in
189
- improper bracketing. Note, in particular, that bracketing may
190
- not be nested.
191
-
192
- :type regexp: str or regexp
193
- :param regexp: A regular expression matching the substring
194
- that should be replaced. This will typically include a
195
- named group, which can be used by ``repl``.
196
- :type repl: str
197
- :param repl: An expression specifying what should replace the
198
- matched substring. Typically, this will include a named
199
- replacement group, specified by ``regexp``.
200
- :rtype: None
201
- :raise ValueError: If this transformation generated an
202
- invalid chunkstring.
203
- """
204
- # Do the actual substitution
205
- s = re.sub(regexp, repl, self._str)
206
-
207
- # The substitution might have generated "empty chunks"
208
- # (substrings of the form "{}"). Remove them, so they don't
209
- # interfere with other transformations.
210
- s = re.sub('\{\}', '', s)
211
-
212
- # Make sure that the transformation was legal.
213
- if self._debug > 1: self._verify(s, self._debug-2)
214
-
215
- # Commit the transformation.
216
- self._str = s
217
-
218
- def __repr__(self):
219
- """
220
- Return a string representation of this ``ChunkString``.
221
- It has the form::
222
-
223
- <ChunkString: '{<DT><JJ><NN>}<VBN><IN>{<DT><NN>}'>
224
-
225
- :rtype: str
226
- """
227
- return '<ChunkString: %s>' % unicode_repr(self._str)
228
-
229
- def __str__(self):
230
- """
231
- Return a formatted representation of this ``ChunkString``.
232
- This representation will include extra spaces to ensure that
233
- tags will line up with the representation of other
234
- ``ChunkStrings`` for the same text, regardless of the chunking.
235
-
236
- :rtype: str
237
- """
238
- # Add spaces to make everything line up.
239
- str = re.sub(r'>(?!\})', r'> ', self._str)
240
- str = re.sub(r'([^\{])<', r'\1 <', str)
241
- if str[0] == '<': str = ' ' + str
242
- return str
243
-
244
- ##//////////////////////////////////////////////////////
245
- ## Chunking Rules
246
- ##//////////////////////////////////////////////////////
247
-
248
- @python_2_unicode_compatible
249
- class RegexpChunkRule(object):
250
- """
251
- A rule specifying how to modify the chunking in a ``ChunkString``,
252
- using a transformational regular expression. The
253
- ``RegexpChunkRule`` class itself can be used to implement any
254
- transformational rule based on regular expressions. There are
255
- also a number of subclasses, which can be used to implement
256
- simpler types of rules, based on matching regular expressions.
257
-
258
- Each ``RegexpChunkRule`` has a regular expression and a
259
- replacement expression. When a ``RegexpChunkRule`` is "applied"
260
- to a ``ChunkString``, it searches the ``ChunkString`` for any
261
- substring that matches the regular expression, and replaces it
262
- using the replacement expression. This search/replace operation
263
- has the same semantics as ``re.sub``.
264
-
265
- Each ``RegexpChunkRule`` also has a description string, which
266
- gives a short (typically less than 75 characters) description of
267
- the purpose of the rule.
268
-
269
- This transformation defined by this ``RegexpChunkRule`` should
270
- only add and remove braces; it should *not* modify the sequence
271
- of angle-bracket delimited tags. Furthermore, this transformation
272
- may not result in nested or mismatched bracketing.
273
- """
274
- def __init__(self, regexp, repl, descr):
275
- """
276
- Construct a new RegexpChunkRule.
277
-
278
- :type regexp: regexp or str
279
- :param regexp: The regular expression for this ``RegexpChunkRule``.
280
- When this rule is applied to a ``ChunkString``, any
281
- substring that matches ``regexp`` will be replaced using
282
- the replacement string ``repl``. Note that this must be a
283
- normal regular expression, not a tag pattern.
284
- :type repl: str
285
- :param repl: The replacement expression for this ``RegexpChunkRule``.
286
- When this rule is applied to a ``ChunkString``, any substring
287
- that matches ``regexp`` will be replaced using ``repl``.
288
- :type descr: str
289
- :param descr: A short description of the purpose and/or effect
290
- of this rule.
291
- """
292
- if isinstance(regexp, string_types):
293
- regexp = re.compile(regexp)
294
- self._repl = repl
295
- self._descr = descr
296
- self._regexp = regexp
297
-
298
- def apply(self, chunkstr):
299
- # Keep docstring generic so we can inherit it.
300
- """
301
- Apply this rule to the given ``ChunkString``. See the
302
- class reference documentation for a description of what it
303
- means to apply a rule.
304
-
305
- :type chunkstr: ChunkString
306
- :param chunkstr: The chunkstring to which this rule is applied.
307
- :rtype: None
308
- :raise ValueError: If this transformation generated an
309
- invalid chunkstring.
310
- """
311
- chunkstr.xform(self._regexp, self._repl)
312
-
313
- def descr(self):
314
- """
315
- Return a short description of the purpose and/or effect of
316
- this rule.
317
-
318
- :rtype: str
319
- """
320
- return self._descr
321
-
322
- def __repr__(self):
323
- """
324
- Return a string representation of this rule. It has the form::
325
-
326
- <RegexpChunkRule: '{<IN|VB.*>}'->'<IN>'>
327
-
328
- Note that this representation does not include the
329
- description string; that string can be accessed
330
- separately with the ``descr()`` method.
331
-
332
- :rtype: str
333
- """
334
- return ('<RegexpChunkRule: '+unicode_repr(self._regexp.pattern)+
335
- '->'+unicode_repr(self._repl)+'>')
336
-
337
- @staticmethod
338
- def fromstring(s):
339
- """
340
- Create a RegexpChunkRule from a string description.
341
- Currently, the following formats are supported::
342
-
343
- {regexp} # chunk rule
344
- }regexp{ # chink rule
345
- regexp}{regexp # split rule
346
- regexp{}regexp # merge rule
347
-
348
- Where ``regexp`` is a regular expression for the rule. Any
349
- text following the comment marker (``#``) will be used as
350
- the rule's description:
351
-
352
- >>> from nltk.chunk.regexp import RegexpChunkRule
353
- >>> RegexpChunkRule.fromstring('{<DT>?<NN.*>+}')
354
- <ChunkRule: '<DT>?<NN.*>+'>
355
- """
356
- # Split off the comment (but don't split on '\#')
357
- m = re.match(r'(?P<rule>(\\.|[^#])*)(?P<comment>#.*)?', s)
358
- rule = m.group('rule').strip()
359
- comment = (m.group('comment') or '')[1:].strip()
360
-
361
- # Pattern bodies: chunk, chink, split, merge
362
- try:
363
- if not rule:
364
- raise ValueError('Empty chunk pattern')
365
- if rule[0] == '{' and rule[-1] == '}':
366
- return ChunkRule(rule[1:-1], comment)
367
- elif rule[0] == '}' and rule[-1] == '{':
368
- return ChinkRule(rule[1:-1], comment)
369
- elif '}{' in rule:
370
- left, right = rule.split('}{')
371
- return SplitRule(left, right, comment)
372
- elif '{}' in rule:
373
- left, right = rule.split('{}')
374
- return MergeRule(left, right, comment)
375
- elif re.match('[^{}]*{[^{}]*}[^{}]*', rule):
376
- left, chunk, right = re.split('[{}]', rule)
377
- return ChunkRuleWithContext(left, chunk, right, comment)
378
- else:
379
- raise ValueError('Illegal chunk pattern: %s' % rule)
380
- except (ValueError, re.error):
381
- raise ValueError('Illegal chunk pattern: %s' % rule)
382
-
383
-
384
- @python_2_unicode_compatible
385
- class ChunkRule(RegexpChunkRule):
386
- """
387
- A rule specifying how to add chunks to a ``ChunkString``, using a
388
- matching tag pattern. When applied to a ``ChunkString``, it will
389
- find any substring that matches this tag pattern and that is not
390
- already part of a chunk, and create a new chunk containing that
391
- substring.
392
- """
393
- def __init__(self, tag_pattern, descr):
394
-
395
- """
396
- Construct a new ``ChunkRule``.
397
-
398
- :type tag_pattern: str
399
- :param tag_pattern: This rule's tag pattern. When
400
- applied to a ``ChunkString``, this rule will
401
- chunk any substring that matches this tag pattern and that
402
- is not already part of a chunk.
403
- :type descr: str
404
- :param descr: A short description of the purpose and/or effect
405
- of this rule.
406
- """
407
- self._pattern = tag_pattern
408
- regexp = re.compile('(?P<chunk>%s)%s' %
409
- (tag_pattern2re_pattern(tag_pattern),
410
- ChunkString.IN_CHINK_PATTERN))
411
- RegexpChunkRule.__init__(self, regexp, '{\g<chunk>}', descr)
412
-
413
- def __repr__(self):
414
- """
415
- Return a string representation of this rule. It has the form::
416
-
417
- <ChunkRule: '<IN|VB.*>'>
418
-
419
- Note that this representation does not include the
420
- description string; that string can be accessed
421
- separately with the ``descr()`` method.
422
-
423
- :rtype: str
424
- """
425
- return '<ChunkRule: '+unicode_repr(self._pattern)+'>'
426
-
427
- @python_2_unicode_compatible
428
- class ChinkRule(RegexpChunkRule):
429
- """
430
- A rule specifying how to remove chinks to a ``ChunkString``,
431
- using a matching tag pattern. When applied to a
432
- ``ChunkString``, it will find any substring that matches this
433
- tag pattern and that is contained in a chunk, and remove it
434
- from that chunk, thus creating two new chunks.
435
- """
436
- def __init__(self, tag_pattern, descr):
437
- """
438
- Construct a new ``ChinkRule``.
439
-
440
- :type tag_pattern: str
441
- :param tag_pattern: This rule's tag pattern. When
442
- applied to a ``ChunkString``, this rule will
443
- find any substring that matches this tag pattern and that
444
- is contained in a chunk, and remove it from that chunk,
445
- thus creating two new chunks.
446
- :type descr: str
447
- :param descr: A short description of the purpose and/or effect
448
- of this rule.
449
- """
450
- self._pattern = tag_pattern
451
- regexp = re.compile('(?P<chink>%s)%s' %
452
- (tag_pattern2re_pattern(tag_pattern),
453
- ChunkString.IN_CHUNK_PATTERN))
454
- RegexpChunkRule.__init__(self, regexp, '}\g<chink>{', descr)
455
-
456
- def __repr__(self):
457
- """
458
- Return a string representation of this rule. It has the form::
459
-
460
- <ChinkRule: '<IN|VB.*>'>
461
-
462
- Note that this representation does not include the
463
- description string; that string can be accessed
464
- separately with the ``descr()`` method.
465
-
466
- :rtype: str
467
- """
468
- return '<ChinkRule: '+unicode_repr(self._pattern)+'>'
469
-
470
-
471
- @python_2_unicode_compatible
472
- class UnChunkRule(RegexpChunkRule):
473
- """
474
- A rule specifying how to remove chunks to a ``ChunkString``,
475
- using a matching tag pattern. When applied to a
476
- ``ChunkString``, it will find any complete chunk that matches this
477
- tag pattern, and un-chunk it.
478
- """
479
- def __init__(self, tag_pattern, descr):
480
- """
481
- Construct a new ``UnChunkRule``.
482
-
483
- :type tag_pattern: str
484
- :param tag_pattern: This rule's tag pattern. When
485
- applied to a ``ChunkString``, this rule will
486
- find any complete chunk that matches this tag pattern,
487
- and un-chunk it.
488
- :type descr: str
489
- :param descr: A short description of the purpose and/or effect
490
- of this rule.
491
- """
492
- self._pattern = tag_pattern
493
- regexp = re.compile('\{(?P<chunk>%s)\}' %
494
- tag_pattern2re_pattern(tag_pattern))
495
- RegexpChunkRule.__init__(self, regexp, '\g<chunk>', descr)
496
-
497
- def __repr__(self):
498
- """
499
- Return a string representation of this rule. It has the form::
500
-
501
- <UnChunkRule: '<IN|VB.*>'>
502
-
503
- Note that this representation does not include the
504
- description string; that string can be accessed
505
- separately with the ``descr()`` method.
506
-
507
- :rtype: str
508
- """
509
- return '<UnChunkRule: '+unicode_repr(self._pattern)+'>'
510
-
511
-
512
- @python_2_unicode_compatible
513
- class MergeRule(RegexpChunkRule):
514
- """
515
- A rule specifying how to merge chunks in a ``ChunkString``, using
516
- two matching tag patterns: a left pattern, and a right pattern.
517
- When applied to a ``ChunkString``, it will find any chunk whose end
518
- matches left pattern, and immediately followed by a chunk whose
519
- beginning matches right pattern. It will then merge those two
520
- chunks into a single chunk.
521
- """
522
- def __init__(self, left_tag_pattern, right_tag_pattern, descr):
523
- """
524
- Construct a new ``MergeRule``.
525
-
526
- :type right_tag_pattern: str
527
- :param right_tag_pattern: This rule's right tag
528
- pattern. When applied to a ``ChunkString``, this
529
- rule will find any chunk whose end matches
530
- ``left_tag_pattern``, and immediately followed by a chunk
531
- whose beginning matches this pattern. It will
532
- then merge those two chunks into a single chunk.
533
- :type left_tag_pattern: str
534
- :param left_tag_pattern: This rule's left tag
535
- pattern. When applied to a ``ChunkString``, this
536
- rule will find any chunk whose end matches
537
- this pattern, and immediately followed by a chunk
538
- whose beginning matches ``right_tag_pattern``. It will
539
- then merge those two chunks into a single chunk.
540
-
541
- :type descr: str
542
- :param descr: A short description of the purpose and/or effect
543
- of this rule.
544
- """
545
- # Ensure that the individual patterns are coherent. E.g., if
546
- # left='(' and right=')', then this will raise an exception:
547
- re.compile(tag_pattern2re_pattern(left_tag_pattern))
548
- re.compile(tag_pattern2re_pattern(right_tag_pattern))
549
-
550
- self._left_tag_pattern = left_tag_pattern
551
- self._right_tag_pattern = right_tag_pattern
552
- regexp = re.compile('(?P<left>%s)}{(?=%s)' %
553
- (tag_pattern2re_pattern(left_tag_pattern),
554
- tag_pattern2re_pattern(right_tag_pattern)))
555
- RegexpChunkRule.__init__(self, regexp, '\g<left>', descr)
556
-
557
- def __repr__(self):
558
- """
559
- Return a string representation of this rule. It has the form::
560
-
561
- <MergeRule: '<NN|DT|JJ>', '<NN|JJ>'>
562
-
563
- Note that this representation does not include the
564
- description string; that string can be accessed
565
- separately with the ``descr()`` method.
566
-
567
- :rtype: str
568
- """
569
- return ('<MergeRule: '+unicode_repr(self._left_tag_pattern)+', '+
570
- unicode_repr(self._right_tag_pattern)+'>')
571
-
572
-
573
- @python_2_unicode_compatible
574
- class SplitRule(RegexpChunkRule):
575
- """
576
- A rule specifying how to split chunks in a ``ChunkString``, using
577
- two matching tag patterns: a left pattern, and a right pattern.
578
- When applied to a ``ChunkString``, it will find any chunk that
579
- matches the left pattern followed by the right pattern. It will
580
- then split the chunk into two new chunks, at the point between the
581
- two pattern matches.
582
- """
583
- def __init__(self, left_tag_pattern, right_tag_pattern, descr):
584
- """
585
- Construct a new ``SplitRule``.
586
-
587
- :type right_tag_pattern: str
588
- :param right_tag_pattern: This rule's right tag
589
- pattern. When applied to a ``ChunkString``, this rule will
590
- find any chunk containing a substring that matches
591
- ``left_tag_pattern`` followed by this pattern. It will
592
- then split the chunk into two new chunks at the point
593
- between these two matching patterns.
594
- :type left_tag_pattern: str
595
- :param left_tag_pattern: This rule's left tag
596
- pattern. When applied to a ``ChunkString``, this rule will
597
- find any chunk containing a substring that matches this
598
- pattern followed by ``right_tag_pattern``. It will then
599
- split the chunk into two new chunks at the point between
600
- these two matching patterns.
601
- :type descr: str
602
- :param descr: A short description of the purpose and/or effect
603
- of this rule.
604
- """
605
- # Ensure that the individual patterns are coherent. E.g., if
606
- # left='(' and right=')', then this will raise an exception:
607
- re.compile(tag_pattern2re_pattern(left_tag_pattern))
608
- re.compile(tag_pattern2re_pattern(right_tag_pattern))
609
-
610
- self._left_tag_pattern = left_tag_pattern
611
- self._right_tag_pattern = right_tag_pattern
612
- regexp = re.compile('(?P<left>%s)(?=%s)' %
613
- (tag_pattern2re_pattern(left_tag_pattern),
614
- tag_pattern2re_pattern(right_tag_pattern)))
615
- RegexpChunkRule.__init__(self, regexp, r'\g<left>}{', descr)
616
-
617
- def __repr__(self):
618
- """
619
- Return a string representation of this rule. It has the form::
620
-
621
- <SplitRule: '<NN>', '<DT>'>
622
-
623
- Note that this representation does not include the
624
- description string; that string can be accessed
625
- separately with the ``descr()`` method.
626
-
627
- :rtype: str
628
- """
629
- return ('<SplitRule: '+unicode_repr(self._left_tag_pattern)+', '+
630
- unicode_repr(self._right_tag_pattern)+'>')
631
-
632
-
633
- @python_2_unicode_compatible
634
- class ExpandLeftRule(RegexpChunkRule):
635
- """
636
- A rule specifying how to expand chunks in a ``ChunkString`` to the left,
637
- using two matching tag patterns: a left pattern, and a right pattern.
638
- When applied to a ``ChunkString``, it will find any chunk whose beginning
639
- matches right pattern, and immediately preceded by a chink whose
640
- end matches left pattern. It will then expand the chunk to incorporate
641
- the new material on the left.
642
- """
643
- def __init__(self, left_tag_pattern, right_tag_pattern, descr):
644
- """
645
- Construct a new ``ExpandRightRule``.
646
-
647
- :type right_tag_pattern: str
648
- :param right_tag_pattern: This rule's right tag
649
- pattern. When applied to a ``ChunkString``, this
650
- rule will find any chunk whose beginning matches
651
- ``right_tag_pattern``, and immediately preceded by a chink
652
- whose end matches this pattern. It will
653
- then merge those two chunks into a single chunk.
654
- :type left_tag_pattern: str
655
- :param left_tag_pattern: This rule's left tag
656
- pattern. When applied to a ``ChunkString``, this
657
- rule will find any chunk whose beginning matches
658
- this pattern, and immediately preceded by a chink
659
- whose end matches ``left_tag_pattern``. It will
660
- then expand the chunk to incorporate the new material on the left.
661
-
662
- :type descr: str
663
- :param descr: A short description of the purpose and/or effect
664
- of this rule.
665
- """
666
- # Ensure that the individual patterns are coherent. E.g., if
667
- # left='(' and right=')', then this will raise an exception:
668
- re.compile(tag_pattern2re_pattern(left_tag_pattern))
669
- re.compile(tag_pattern2re_pattern(right_tag_pattern))
670
-
671
- self._left_tag_pattern = left_tag_pattern
672
- self._right_tag_pattern = right_tag_pattern
673
- regexp = re.compile('(?P<left>%s)\{(?P<right>%s)' %
674
- (tag_pattern2re_pattern(left_tag_pattern),
675
- tag_pattern2re_pattern(right_tag_pattern)))
676
- RegexpChunkRule.__init__(self, regexp, '{\g<left>\g<right>', descr)
677
-
678
- def __repr__(self):
679
- """
680
- Return a string representation of this rule. It has the form::
681
-
682
- <ExpandLeftRule: '<NN|DT|JJ>', '<NN|JJ>'>
683
-
684
- Note that this representation does not include the
685
- description string; that string can be accessed
686
- separately with the ``descr()`` method.
687
-
688
- :rtype: str
689
- """
690
- return ('<ExpandLeftRule: '+unicode_repr(self._left_tag_pattern)+', '+
691
- unicode_repr(self._right_tag_pattern)+'>')
692
-
693
-
694
- @python_2_unicode_compatible
695
- class ExpandRightRule(RegexpChunkRule):
696
- """
697
- A rule specifying how to expand chunks in a ``ChunkString`` to the
698
- right, using two matching tag patterns: a left pattern, and a
699
- right pattern. When applied to a ``ChunkString``, it will find any
700
- chunk whose end matches left pattern, and immediately followed by
701
- a chink whose beginning matches right pattern. It will then
702
- expand the chunk to incorporate the new material on the right.
703
- """
704
- def __init__(self, left_tag_pattern, right_tag_pattern, descr):
705
- """
706
- Construct a new ``ExpandRightRule``.
707
-
708
- :type right_tag_pattern: str
709
- :param right_tag_pattern: This rule's right tag
710
- pattern. When applied to a ``ChunkString``, this
711
- rule will find any chunk whose end matches
712
- ``left_tag_pattern``, and immediately followed by a chink
713
- whose beginning matches this pattern. It will
714
- then merge those two chunks into a single chunk.
715
- :type left_tag_pattern: str
716
- :param left_tag_pattern: This rule's left tag
717
- pattern. When applied to a ``ChunkString``, this
718
- rule will find any chunk whose end matches
719
- this pattern, and immediately followed by a chink
720
- whose beginning matches ``right_tag_pattern``. It will
721
- then expand the chunk to incorporate the new material on the right.
722
-
723
- :type descr: str
724
- :param descr: A short description of the purpose and/or effect
725
- of this rule.
726
- """
727
- # Ensure that the individual patterns are coherent. E.g., if
728
- # left='(' and right=')', then this will raise an exception:
729
- re.compile(tag_pattern2re_pattern(left_tag_pattern))
730
- re.compile(tag_pattern2re_pattern(right_tag_pattern))
731
-
732
- self._left_tag_pattern = left_tag_pattern
733
- self._right_tag_pattern = right_tag_pattern
734
- regexp = re.compile('(?P<left>%s)\}(?P<right>%s)' %
735
- (tag_pattern2re_pattern(left_tag_pattern),
736
- tag_pattern2re_pattern(right_tag_pattern)))
737
- RegexpChunkRule.__init__(self, regexp, '\g<left>\g<right>}', descr)
738
-
739
- def __repr__(self):
740
- """
741
- Return a string representation of this rule. It has the form::
742
-
743
- <ExpandRightRule: '<NN|DT|JJ>', '<NN|JJ>'>
744
-
745
- Note that this representation does not include the
746
- description string; that string can be accessed
747
- separately with the ``descr()`` method.
748
-
749
- :rtype: str
750
- """
751
- return ('<ExpandRightRule: '+unicode_repr(self._left_tag_pattern)+', '+
752
- unicode_repr(self._right_tag_pattern)+'>')
753
-
754
-
755
- @python_2_unicode_compatible
756
- class ChunkRuleWithContext(RegexpChunkRule):
757
- """
758
- A rule specifying how to add chunks to a ``ChunkString``, using
759
- three matching tag patterns: one for the left context, one for the
760
- chunk, and one for the right context. When applied to a
761
- ``ChunkString``, it will find any substring that matches the chunk
762
- tag pattern, is surrounded by substrings that match the two
763
- context patterns, and is not already part of a chunk; and create a
764
- new chunk containing the substring that matched the chunk tag
765
- pattern.
766
-
767
- Caveat: Both the left and right context are consumed when this
768
- rule matches; therefore, if you need to find overlapping matches,
769
- you will need to apply your rule more than once.
770
- """
771
- def __init__(self, left_context_tag_pattern, chunk_tag_pattern,
772
- right_context_tag_pattern, descr):
773
- """
774
- Construct a new ``ChunkRuleWithContext``.
775
-
776
- :type left_context_tag_pattern: str
777
- :param left_context_tag_pattern: A tag pattern that must match
778
- the left context of ``chunk_tag_pattern`` for this rule to
779
- apply.
780
- :type chunk_tag_pattern: str
781
- :param chunk_tag_pattern: A tag pattern that must match for this
782
- rule to apply. If the rule does apply, then this pattern
783
- also identifies the substring that will be made into a chunk.
784
- :type right_context_tag_pattern: str
785
- :param right_context_tag_pattern: A tag pattern that must match
786
- the right context of ``chunk_tag_pattern`` for this rule to
787
- apply.
788
- :type descr: str
789
- :param descr: A short description of the purpose and/or effect
790
- of this rule.
791
- """
792
- # Ensure that the individual patterns are coherent. E.g., if
793
- # left='(' and right=')', then this will raise an exception:
794
- re.compile(tag_pattern2re_pattern(left_context_tag_pattern))
795
- re.compile(tag_pattern2re_pattern(chunk_tag_pattern))
796
- re.compile(tag_pattern2re_pattern(right_context_tag_pattern))
797
-
798
- self._left_context_tag_pattern = left_context_tag_pattern
799
- self._chunk_tag_pattern = chunk_tag_pattern
800
- self._right_context_tag_pattern = right_context_tag_pattern
801
- regexp = re.compile('(?P<left>%s)(?P<chunk>%s)(?P<right>%s)%s' %
802
- (tag_pattern2re_pattern(left_context_tag_pattern),
803
- tag_pattern2re_pattern(chunk_tag_pattern),
804
- tag_pattern2re_pattern(right_context_tag_pattern),
805
- ChunkString.IN_CHINK_PATTERN))
806
- replacement = r'\g<left>{\g<chunk>}\g<right>'
807
- RegexpChunkRule.__init__(self, regexp, replacement, descr)
808
-
809
- def __repr__(self):
810
- """
811
- Return a string representation of this rule. It has the form::
812
-
813
- <ChunkRuleWithContext: '<IN>', '<NN>', '<DT>'>
814
-
815
- Note that this representation does not include the
816
- description string; that string can be accessed
817
- separately with the ``descr()`` method.
818
-
819
- :rtype: str
820
- """
821
- return '<ChunkRuleWithContext: %r, %r, %r>' % (
822
- self._left_context_tag_pattern, self._chunk_tag_pattern,
823
- self._right_context_tag_pattern)
824
-
825
- ##//////////////////////////////////////////////////////
826
- ## Tag Pattern Format Conversion
827
- ##//////////////////////////////////////////////////////
828
-
829
- # this should probably be made more strict than it is -- e.g., it
830
- # currently accepts 'foo'.
831
- CHUNK_TAG_PATTERN = re.compile(r'^((%s|<%s>)*)$' %
832
- ('[^\{\}<>]+',
833
- '[^\{\}<>]+'))
834
-
835
- def tag_pattern2re_pattern(tag_pattern):
836
- """
837
- Convert a tag pattern to a regular expression pattern. A "tag
838
- pattern" is a modified version of a regular expression, designed
839
- for matching sequences of tags. The differences between regular
840
- expression patterns and tag patterns are:
841
-
842
- - In tag patterns, ``'<'`` and ``'>'`` act as parentheses; so
843
- ``'<NN>+'`` matches one or more repetitions of ``'<NN>'``, not
844
- ``'<NN'`` followed by one or more repetitions of ``'>'``.
845
- - Whitespace in tag patterns is ignored. So
846
- ``'<DT> | <NN>'`` is equivalant to ``'<DT>|<NN>'``
847
- - In tag patterns, ``'.'`` is equivalant to ``'[^{}<>]'``; so
848
- ``'<NN.*>'`` matches any single tag starting with ``'NN'``.
849
-
850
- In particular, ``tag_pattern2re_pattern`` performs the following
851
- transformations on the given pattern:
852
-
853
- - Replace '.' with '[^<>{}]'
854
- - Remove any whitespace
855
- - Add extra parens around '<' and '>', to make '<' and '>' act
856
- like parentheses. E.g., so that in '<NN>+', the '+' has scope
857
- over the entire '<NN>'; and so that in '<NN|IN>', the '|' has
858
- scope over 'NN' and 'IN', but not '<' or '>'.
859
- - Check to make sure the resulting pattern is valid.
860
-
861
- :type tag_pattern: str
862
- :param tag_pattern: The tag pattern to convert to a regular
863
- expression pattern.
864
- :raise ValueError: If ``tag_pattern`` is not a valid tag pattern.
865
- In particular, ``tag_pattern`` should not include braces; and it
866
- should not contain nested or mismatched angle-brackets.
867
- :rtype: str
868
- :return: A regular expression pattern corresponding to
869
- ``tag_pattern``.
870
- """
871
- # Clean up the regular expression
872
- tag_pattern = re.sub(r'\s', '', tag_pattern)
873
- tag_pattern = re.sub(r'<', '(<(', tag_pattern)
874
- tag_pattern = re.sub(r'>', ')>)', tag_pattern)
875
-
876
- # Check the regular expression
877
- if not CHUNK_TAG_PATTERN.match(tag_pattern):
878
- raise ValueError('Bad tag pattern: %r' % tag_pattern)
879
-
880
- # Replace "." with CHUNK_TAG_CHAR.
881
- # We have to do this after, since it adds {}[]<>s, which would
882
- # confuse CHUNK_TAG_PATTERN.
883
- # PRE doesn't have lookback assertions, so reverse twice, and do
884
- # the pattern backwards (with lookahead assertions). This can be
885
- # made much cleaner once we can switch back to SRE.
886
- def reverse_str(str):
887
- lst = list(str)
888
- lst.reverse()
889
- return ''.join(lst)
890
- tc_rev = reverse_str(ChunkString.CHUNK_TAG_CHAR)
891
- reversed = reverse_str(tag_pattern)
892
- reversed = re.sub(r'\.(?!\\(\\\\)*($|[^\\]))', tc_rev, reversed)
893
- tag_pattern = reverse_str(reversed)
894
-
895
- return tag_pattern
896
-
897
-
898
- ##//////////////////////////////////////////////////////
899
- ## RegexpChunkParser
900
- ##//////////////////////////////////////////////////////
901
-
902
- @python_2_unicode_compatible
903
- class RegexpChunkParser(ChunkParserI):
904
- """
905
- A regular expression based chunk parser. ``RegexpChunkParser`` uses a
906
- sequence of "rules" to find chunks of a single type within a
907
- text. The chunking of the text is encoded using a ``ChunkString``,
908
- and each rule acts by modifying the chunking in the
909
- ``ChunkString``. The rules are all implemented using regular
910
- expression matching and substitution.
911
-
912
- The ``RegexpChunkRule`` class and its subclasses (``ChunkRule``,
913
- ``ChinkRule``, ``UnChunkRule``, ``MergeRule``, and ``SplitRule``)
914
- define the rules that are used by ``RegexpChunkParser``. Each rule
915
- defines an ``apply()`` method, which modifies the chunking encoded
916
- by a given ``ChunkString``.
917
-
918
- :type _rules: list(RegexpChunkRule)
919
- :ivar _rules: The list of rules that should be applied to a text.
920
- :type _trace: int
921
- :ivar _trace: The default level of tracing.
922
-
923
- """
924
- def __init__(self, rules, chunk_label='NP', root_label='S', trace=0):
925
- """
926
- Construct a new ``RegexpChunkParser``.
927
-
928
- :type rules: list(RegexpChunkRule)
929
- :param rules: The sequence of rules that should be used to
930
- generate the chunking for a tagged text.
931
- :type chunk_label: str
932
- :param chunk_label: The node value that should be used for
933
- chunk subtrees. This is typically a short string
934
- describing the type of information contained by the chunk,
935
- such as ``"NP"`` for base noun phrases.
936
- :type root_label: str
937
- :param root_label: The node value that should be used for the
938
- top node of the chunk structure.
939
- :type trace: int
940
- :param trace: The level of tracing that should be used when
941
- parsing a text. ``0`` will generate no tracing output;
942
- ``1`` will generate normal tracing output; and ``2`` or
943
- higher will generate verbose tracing output.
944
- """
945
- self._rules = rules
946
- self._trace = trace
947
- self._chunk_label = chunk_label
948
- self._root_label = root_label
949
-
950
- def _trace_apply(self, chunkstr, verbose):
951
- """
952
- Apply each rule of this ``RegexpChunkParser`` to ``chunkstr``, in
953
- turn. Generate trace output between each rule. If ``verbose``
954
- is true, then generate verbose output.
955
-
956
- :type chunkstr: ChunkString
957
- :param chunkstr: The chunk string to which each rule should be
958
- applied.
959
- :type verbose: bool
960
- :param verbose: Whether output should be verbose.
961
- :rtype: None
962
- """
963
- print('# Input:')
964
- print(chunkstr)
965
- for rule in self._rules:
966
- rule.apply(chunkstr)
967
- if verbose:
968
- print('#', rule.descr()+' ('+unicode_repr(rule)+'):')
969
- else:
970
- print('#', rule.descr()+':')
971
- print(chunkstr)
972
-
973
- def _notrace_apply(self, chunkstr):
974
- """
975
- Apply each rule of this ``RegexpChunkParser`` to ``chunkstr``, in
976
- turn.
977
-
978
- :param chunkstr: The chunk string to which each rule should be
979
- applied.
980
- :type chunkstr: ChunkString
981
- :rtype: None
982
- """
983
-
984
- for rule in self._rules:
985
- rule.apply(chunkstr)
986
-
987
- def parse(self, chunk_struct, trace=None):
988
- """
989
- :type chunk_struct: Tree
990
- :param chunk_struct: the chunk structure to be (further) chunked
991
- :type trace: int
992
- :param trace: The level of tracing that should be used when
993
- parsing a text. ``0`` will generate no tracing output;
994
- ``1`` will generate normal tracing output; and ``2`` or
995
- highter will generate verbose tracing output. This value
996
- overrides the trace level value that was given to the
997
- constructor.
998
- :rtype: Tree
999
- :return: a chunk structure that encodes the chunks in a given
1000
- tagged sentence. A chunk is a non-overlapping linguistic
1001
- group, such as a noun phrase. The set of chunks
1002
- identified in the chunk structure depends on the rules
1003
- used to define this ``RegexpChunkParser``.
1004
- """
1005
- if len(chunk_struct) == 0:
1006
- print('Warning: parsing empty text')
1007
- return Tree(self._root_label, [])
1008
-
1009
- try:
1010
- chunk_struct.label()
1011
- except AttributeError:
1012
- chunk_struct = Tree(self._root_label, chunk_struct)
1013
-
1014
- # Use the default trace value?
1015
- if trace is None: trace = self._trace
1016
-
1017
- chunkstr = ChunkString(chunk_struct)
1018
-
1019
- # Apply the sequence of rules to the chunkstring.
1020
- if trace:
1021
- verbose = (trace>1)
1022
- self._trace_apply(chunkstr, verbose)
1023
- else:
1024
- self._notrace_apply(chunkstr)
1025
-
1026
- # Use the chunkstring to create a chunk structure.
1027
- return chunkstr.to_chunkstruct(self._chunk_label)
1028
-
1029
- def rules(self):
1030
- """
1031
- :return: the sequence of rules used by ``RegexpChunkParser``.
1032
- :rtype: list(RegexpChunkRule)
1033
- """
1034
- return self._rules
1035
-
1036
- def __repr__(self):
1037
- """
1038
- :return: a concise string representation of this
1039
- ``RegexpChunkParser``.
1040
- :rtype: str
1041
- """
1042
- return "<RegexpChunkParser with %d rules>" % len(self._rules)
1043
-
1044
- def __str__(self):
1045
- """
1046
- :return: a verbose string representation of this ``RegexpChunkParser``.
1047
- :rtype: str
1048
- """
1049
- s = "RegexpChunkParser with %d rules:\n" % len(self._rules)
1050
- margin = 0
1051
- for rule in self._rules:
1052
- margin = max(margin, len(rule.descr()))
1053
- if margin < 35:
1054
- format = " %" + repr(-(margin+3)) + "s%s\n"
1055
- else:
1056
- format = " %s\n %s\n"
1057
- for rule in self._rules:
1058
- s += format % (rule.descr(), unicode_repr(rule))
1059
- return s[:-1]
1060
-
1061
- ##//////////////////////////////////////////////////////
1062
- ## Chunk Grammar
1063
- ##//////////////////////////////////////////////////////
1064
-
1065
- @python_2_unicode_compatible
1066
- class RegexpParser(ChunkParserI):
1067
- """
1068
- A grammar based chunk parser. ``chunk.RegexpParser`` uses a set of
1069
- regular expression patterns to specify the behavior of the parser.
1070
- The chunking of the text is encoded using a ``ChunkString``, and
1071
- each rule acts by modifying the chunking in the ``ChunkString``.
1072
- The rules are all implemented using regular expression matching
1073
- and substitution.
1074
-
1075
- A grammar contains one or more clauses in the following form::
1076
-
1077
- NP:
1078
- {<DT|JJ>} # chunk determiners and adjectives
1079
- }<[\.VI].*>+{ # chink any tag beginning with V, I, or .
1080
- <.*>}{<DT> # split a chunk at a determiner
1081
- <DT|JJ>{}<NN.*> # merge chunk ending with det/adj
1082
- # with one starting with a noun
1083
-
1084
- The patterns of a clause are executed in order. An earlier
1085
- pattern may introduce a chunk boundary that prevents a later
1086
- pattern from executing. Sometimes an individual pattern will
1087
- match on multiple, overlapping extents of the input. As with
1088
- regular expression substitution more generally, the chunker will
1089
- identify the first match possible, then continue looking for matches
1090
- after this one has ended.
1091
-
1092
- The clauses of a grammar are also executed in order. A cascaded
1093
- chunk parser is one having more than one clause. The maximum depth
1094
- of a parse tree created by this chunk parser is the same as the
1095
- number of clauses in the grammar.
1096
-
1097
- When tracing is turned on, the comment portion of a line is displayed
1098
- each time the corresponding pattern is applied.
1099
-
1100
- :type _start: str
1101
- :ivar _start: The start symbol of the grammar (the root node of
1102
- resulting trees)
1103
- :type _stages: int
1104
- :ivar _stages: The list of parsing stages corresponding to the grammar
1105
-
1106
- """
1107
- def __init__(self, grammar, root_label='S', loop=1, trace=0):
1108
- """
1109
- Create a new chunk parser, from the given start state
1110
- and set of chunk patterns.
1111
-
1112
- :param grammar: The grammar, or a list of RegexpChunkParser objects
1113
- :type grammar: str or list(RegexpChunkParser)
1114
- :param root_label: The top node of the tree being created
1115
- :type root_label: str or Nonterminal
1116
- :param loop: The number of times to run through the patterns
1117
- :type loop: int
1118
- :type trace: int
1119
- :param trace: The level of tracing that should be used when
1120
- parsing a text. ``0`` will generate no tracing output;
1121
- ``1`` will generate normal tracing output; and ``2`` or
1122
- higher will generate verbose tracing output.
1123
- """
1124
- self._trace = trace
1125
- self._stages = []
1126
- self._grammar = grammar
1127
- self._loop = loop
1128
-
1129
- if isinstance(grammar, string_types):
1130
- self._read_grammar(grammar, root_label, trace)
1131
- else:
1132
- # Make sur the grammar looks like it has the right type:
1133
- type_err = ('Expected string or list of RegexpChunkParsers '
1134
- 'for the grammar.')
1135
- try: grammar = list(grammar)
1136
- except: raise TypeError(type_err)
1137
- for elt in grammar:
1138
- if not isinstance(elt, RegexpChunkParser):
1139
- raise TypeError(type_err)
1140
- self._stages = grammar
1141
-
1142
- def _read_grammar(self, grammar, root_label, trace):
1143
- """
1144
- Helper function for __init__: read the grammar if it is a
1145
- string.
1146
- """
1147
- rules = []
1148
- lhs = None
1149
- for line in grammar.split('\n'):
1150
- line = line.strip()
1151
-
1152
- # New stage begins if there's an unescaped ':'
1153
- m = re.match('(?P<nonterminal>(\\.|[^:])*)(:(?P<rule>.*))', line)
1154
- if m:
1155
- # Record the stage that we just completed.
1156
- self._add_stage(rules, lhs, root_label, trace)
1157
- # Start a new stage.
1158
- lhs = m.group('nonterminal').strip()
1159
- rules = []
1160
- line = m.group('rule').strip()
1161
-
1162
- # Skip blank & comment-only lines
1163
- if line=='' or line.startswith('#'): continue
1164
-
1165
- # Add the rule
1166
- rules.append(RegexpChunkRule.fromstring(line))
1167
-
1168
- # Record the final stage
1169
- self._add_stage(rules, lhs, root_label, trace)
1170
-
1171
- def _add_stage(self, rules, lhs, root_label, trace):
1172
- """
1173
- Helper function for __init__: add a new stage to the parser.
1174
- """
1175
- if rules != []:
1176
- if not lhs:
1177
- raise ValueError('Expected stage marker (eg NP:)')
1178
- parser = RegexpChunkParser(rules, chunk_label=lhs,
1179
- root_label=root_label, trace=trace)
1180
- self._stages.append(parser)
1181
-
1182
- def parse(self, chunk_struct, trace=None):
1183
- """
1184
- Apply the chunk parser to this input.
1185
-
1186
- :type chunk_struct: Tree
1187
- :param chunk_struct: the chunk structure to be (further) chunked
1188
- (this tree is modified, and is also returned)
1189
- :type trace: int
1190
- :param trace: The level of tracing that should be used when
1191
- parsing a text. ``0`` will generate no tracing output;
1192
- ``1`` will generate normal tracing output; and ``2`` or
1193
- highter will generate verbose tracing output. This value
1194
- overrides the trace level value that was given to the
1195
- constructor.
1196
- :return: the chunked output.
1197
- :rtype: Tree
1198
- """
1199
- if trace is None: trace = self._trace
1200
- for i in range(self._loop):
1201
- for parser in self._stages:
1202
- chunk_struct = parser.parse(chunk_struct, trace=trace)
1203
- return chunk_struct
1204
-
1205
- def __repr__(self):
1206
- """
1207
- :return: a concise string representation of this ``chunk.RegexpParser``.
1208
- :rtype: str
1209
- """
1210
- return "<chunk.RegexpParser with %d stages>" % len(self._stages)
1211
-
1212
- def __str__(self):
1213
- """
1214
- :return: a verbose string representation of this
1215
- ``RegexpParser``.
1216
- :rtype: str
1217
- """
1218
- s = "chunk.RegexpParser with %d stages:\n" % len(self._stages)
1219
- margin = 0
1220
- for parser in self._stages:
1221
- s += "%s\n" % parser
1222
- return s[:-1]
1223
-
1224
- ##//////////////////////////////////////////////////////
1225
- ## Demonstration code
1226
- ##//////////////////////////////////////////////////////
1227
-
1228
- def demo_eval(chunkparser, text):
1229
- """
1230
- Demonstration code for evaluating a chunk parser, using a
1231
- ``ChunkScore``. This function assumes that ``text`` contains one
1232
- sentence per line, and that each sentence has the form expected by
1233
- ``tree.chunk``. It runs the given chunk parser on each sentence in
1234
- the text, and scores the result. It prints the final score
1235
- (precision, recall, and f-measure); and reports the set of chunks
1236
- that were missed and the set of chunks that were incorrect. (At
1237
- most 10 missing chunks and 10 incorrect chunks are reported).
1238
-
1239
- :param chunkparser: The chunkparser to be tested
1240
- :type chunkparser: ChunkParserI
1241
- :param text: The chunked tagged text that should be used for
1242
- evaluation.
1243
- :type text: str
1244
- """
1245
- from nltk import chunk
1246
- from nltk.tree import Tree
1247
-
1248
- # Evaluate our chunk parser.
1249
- chunkscore = chunk.ChunkScore()
1250
-
1251
- for sentence in text.split('\n'):
1252
- print(sentence)
1253
- sentence = sentence.strip()
1254
- if not sentence: continue
1255
- gold = chunk.tagstr2tree(sentence)
1256
- tokens = gold.leaves()
1257
- test = chunkparser.parse(Tree('S', tokens), trace=1)
1258
- chunkscore.score(gold, test)
1259
- print()
1260
-
1261
- print('/'+('='*75)+'\\')
1262
- print('Scoring', chunkparser)
1263
- print(('-'*77))
1264
- print('Precision: %5.1f%%' % (chunkscore.precision()*100), ' '*4, end=' ')
1265
- print('Recall: %5.1f%%' % (chunkscore.recall()*100), ' '*6, end=' ')
1266
- print('F-Measure: %5.1f%%' % (chunkscore.f_measure()*100))
1267
-
1268
-
1269
- # Missed chunks.
1270
- if chunkscore.missed():
1271
- print('Missed:')
1272
- missed = chunkscore.missed()
1273
- for chunk in missed[:10]:
1274
- print(' ', ' '.join(map(str,chunk)))
1275
- if len(chunkscore.missed()) > 10:
1276
- print(' ...')
1277
-
1278
- # Incorrect chunks.
1279
- if chunkscore.incorrect():
1280
- print('Incorrect:')
1281
- incorrect = chunkscore.incorrect()
1282
- for chunk in incorrect[:10]:
1283
- print(' ', ' '.join(map(str,chunk)))
1284
- if len(chunkscore.incorrect()) > 10:
1285
- print(' ...')
1286
-
1287
- print('\\'+('='*75)+'/')
1288
- print()
1289
-
1290
- def demo():
1291
- """
1292
- A demonstration for the ``RegexpChunkParser`` class. A single text is
1293
- parsed with four different chunk parsers, using a variety of rules
1294
- and strategies.
1295
- """
1296
-
1297
- from nltk import chunk, Tree
1298
-
1299
- text = """\
1300
- [ the/DT little/JJ cat/NN ] sat/VBD on/IN [ the/DT mat/NN ] ./.
1301
- [ John/NNP ] saw/VBD [the/DT cats/NNS] [the/DT dog/NN] chased/VBD ./.
1302
- [ John/NNP ] thinks/VBZ [ Mary/NN ] saw/VBD [ the/DT cat/NN ] sit/VB on/IN [ the/DT mat/NN ]./.
1303
- """
1304
-
1305
- print('*'*75)
1306
- print('Evaluation text:')
1307
- print(text)
1308
- print('*'*75)
1309
- print()
1310
-
1311
- grammar = r"""
1312
- NP: # NP stage
1313
- {<DT>?<JJ>*<NN>} # chunk determiners, adjectives and nouns
1314
- {<NNP>+} # chunk proper nouns
1315
- """
1316
- cp = chunk.RegexpParser(grammar)
1317
- demo_eval(cp, text)
1318
-
1319
- grammar = r"""
1320
- NP:
1321
- {<.*>} # start by chunking each tag
1322
- }<[\.VI].*>+{ # unchunk any verbs, prepositions or periods
1323
- <DT|JJ>{}<NN.*> # merge det/adj with nouns
1324
- """
1325
- cp = chunk.RegexpParser(grammar)
1326
- demo_eval(cp, text)
1327
-
1328
- grammar = r"""
1329
- NP: {<DT>?<JJ>*<NN>} # chunk determiners, adjectives and nouns
1330
- VP: {<TO>?<VB.*>} # VP = verb words
1331
- """
1332
- cp = chunk.RegexpParser(grammar)
1333
- demo_eval(cp, text)
1334
-
1335
- grammar = r"""
1336
- NP: {<.*>*} # start by chunking everything
1337
- }<[\.VI].*>+{ # chink any verbs, prepositions or periods
1338
- <.*>}{<DT> # separate on determiners
1339
- PP: {<IN><NP>} # PP = preposition + noun phrase
1340
- VP: {<VB.*><NP|PP>*} # VP = verb words + NPs and PPs
1341
- """
1342
- cp = chunk.RegexpParser(grammar)
1343
- demo_eval(cp, text)
1344
-
1345
- # Evaluation
1346
-
1347
- from nltk.corpus import conll2000
1348
-
1349
- print()
1350
- print("Demonstration of empty grammar:")
1351
-
1352
- cp = chunk.RegexpParser("")
1353
- print(chunk.accuracy(cp, conll2000.chunked_sents('test.txt',
1354
- chunk_types=('NP',))))
1355
-
1356
- print()
1357
- print("Demonstration of accuracy evaluation using CoNLL tags:")
1358
-
1359
- grammar = r"""
1360
- NP:
1361
- {<.*>} # start by chunking each tag
1362
- }<[\.VI].*>+{ # unchunk any verbs, prepositions or periods
1363
- <DT|JJ>{}<NN.*> # merge det/adj with nouns
1364
- """
1365
- cp = chunk.RegexpParser(grammar)
1366
- print(chunk.accuracy(cp, conll2000.chunked_sents('test.txt')[:5]))
1367
-
1368
- print()
1369
- print("Demonstration of tagged token input")
1370
-
1371
- grammar = r"""
1372
- NP: {<.*>*} # start by chunking everything
1373
- }<[\.VI].*>+{ # chink any verbs, prepositions or periods
1374
- <.*>}{<DT> # separate on determiners
1375
- PP: {<IN><NP>} # PP = preposition + noun phrase
1376
- VP: {<VB.*><NP|PP>*} # VP = verb words + NPs and PPs
1377
- """
1378
- cp = chunk.RegexpParser(grammar)
1379
- print(cp.parse([("the","DT"), ("little","JJ"), ("cat", "NN"),
1380
- ("sat", "VBD"), ("on", "IN"), ("the", "DT"),
1381
- ("mat", "NN"), (".", ".")]))
1382
-
1383
- if __name__ == '__main__':
1384
- demo()
1
+ # Natural Language Toolkit: Regular Expression Chunkers
2
+ #
3
+ # Copyright (C) 2001-2018 NLTK Project
4
+ # Author: Edward Loper <edloper@gmail.com>
5
+ # Steven Bird <stevenbird1@gmail.com> (minor additions)
6
+ # URL: <http://nltk.org/>
7
+ # For license information, see LICENSE.TXT
8
+ from __future__ import print_function, unicode_literals
9
+ from __future__ import division
10
+
11
+ import re
12
+
13
+ from six import string_types
14
+
15
+ from nltk.tree import Tree
16
+ from nltk.chunk.api import ChunkParserI
17
+ from nltk.compat import python_2_unicode_compatible, unicode_repr
18
+
19
+ ##//////////////////////////////////////////////////////
20
+ ## ChunkString
21
+ ##//////////////////////////////////////////////////////
22
+
23
+ @python_2_unicode_compatible
24
+ class ChunkString(object):
25
+ """
26
+ A string-based encoding of a particular chunking of a text.
27
+ Internally, the ``ChunkString`` class uses a single string to
28
+ encode the chunking of the input text. This string contains a
29
+ sequence of angle-bracket delimited tags, with chunking indicated
30
+ by braces. An example of this encoding is::
31
+
32
+ {<DT><JJ><NN>}<VBN><IN>{<DT><NN>}<.>{<DT><NN>}<VBD><.>
33
+
34
+ ``ChunkString`` are created from tagged texts (i.e., lists of
35
+ ``tokens`` whose type is ``TaggedType``). Initially, nothing is
36
+ chunked.
37
+
38
+ The chunking of a ``ChunkString`` can be modified with the ``xform()``
39
+ method, which uses a regular expression to transform the string
40
+ representation. These transformations should only add and remove
41
+ braces; they should *not* modify the sequence of angle-bracket
42
+ delimited tags.
43
+
44
+ :type _str: str
45
+ :ivar _str: The internal string representation of the text's
46
+ encoding. This string representation contains a sequence of
47
+ angle-bracket delimited tags, with chunking indicated by
48
+ braces. An example of this encoding is::
49
+
50
+ {<DT><JJ><NN>}<VBN><IN>{<DT><NN>}<.>{<DT><NN>}<VBD><.>
51
+
52
+ :type _pieces: list(tagged tokens and chunks)
53
+ :ivar _pieces: The tagged tokens and chunks encoded by this ``ChunkString``.
54
+ :ivar _debug: The debug level. See the constructor docs.
55
+
56
+ :cvar IN_CHUNK_PATTERN: A zero-width regexp pattern string that
57
+ will only match positions that are in chunks.
58
+ :cvar IN_CHINK_PATTERN: A zero-width regexp pattern string that
59
+ will only match positions that are in chinks.
60
+ """
61
+ CHUNK_TAG_CHAR = r'[^\{\}<>]'
62
+ CHUNK_TAG = r'(<%s+?>)' % CHUNK_TAG_CHAR
63
+
64
+ IN_CHUNK_PATTERN = r'(?=[^\{]*\})'
65
+ IN_CHINK_PATTERN = r'(?=[^\}]*(\{|$))'
66
+
67
+ # These are used by _verify
68
+ _CHUNK = r'(\{%s+?\})+?' % CHUNK_TAG
69
+ _CHINK = r'(%s+?)+?' % CHUNK_TAG
70
+ _VALID = re.compile(r'^(\{?%s\}?)*?$' % CHUNK_TAG)
71
+ _BRACKETS = re.compile('[^\{\}]+')
72
+ _BALANCED_BRACKETS = re.compile(r'(\{\})*$')
73
+
74
+ def __init__(self, chunk_struct, debug_level=1):
75
+ """
76
+ Construct a new ``ChunkString`` that encodes the chunking of
77
+ the text ``tagged_tokens``.
78
+
79
+ :type chunk_struct: Tree
80
+ :param chunk_struct: The chunk structure to be further chunked.
81
+ :type debug_level: int
82
+ :param debug_level: The level of debugging which should be
83
+ applied to transformations on the ``ChunkString``. The
84
+ valid levels are:
85
+ - 0: no checks
86
+ - 1: full check on to_chunkstruct
87
+ - 2: full check on to_chunkstruct and cursory check after
88
+ each transformation.
89
+ - 3: full check on to_chunkstruct and full check after
90
+ each transformation.
91
+ We recommend you use at least level 1. You should
92
+ probably use level 3 if you use any non-standard
93
+ subclasses of ``RegexpChunkRule``.
94
+ """
95
+ self._root_label = chunk_struct.label()
96
+ self._pieces = chunk_struct[:]
97
+ tags = [self._tag(tok) for tok in self._pieces]
98
+ self._str = '<' + '><'.join(tags) + '>'
99
+ self._debug = debug_level
100
+
101
+ def _tag(self, tok):
102
+ if isinstance(tok, tuple):
103
+ return tok[1]
104
+ elif isinstance(tok, Tree):
105
+ return tok.label()
106
+ else:
107
+ raise ValueError('chunk structures must contain tagged '
108
+ 'tokens or trees')
109
+
110
+ def _verify(self, s, verify_tags):
111
+ """
112
+ Check to make sure that ``s`` still corresponds to some chunked
113
+ version of ``_pieces``.
114
+
115
+ :type verify_tags: bool
116
+ :param verify_tags: Whether the individual tags should be
117
+ checked. If this is false, ``_verify`` will check to make
118
+ sure that ``_str`` encodes a chunked version of *some*
119
+ list of tokens. If this is true, then ``_verify`` will
120
+ check to make sure that the tags in ``_str`` match those in
121
+ ``_pieces``.
122
+
123
+ :raise ValueError: if the internal string representation of
124
+ this ``ChunkString`` is invalid or not consistent with _pieces.
125
+ """
126
+ # Check overall form
127
+ if not ChunkString._VALID.match(s):
128
+ raise ValueError('Transformation generated invalid '
129
+ 'chunkstring:\n %s' % s)
130
+
131
+ # Check that parens are balanced. If the string is long, we
132
+ # have to do this in pieces, to avoid a maximum recursion
133
+ # depth limit for regular expressions.
134
+ brackets = ChunkString._BRACKETS.sub('', s)
135
+ for i in range(1 + len(brackets) // 5000):
136
+ substr = brackets[i*5000:i*5000+5000]
137
+ if not ChunkString._BALANCED_BRACKETS.match(substr):
138
+ raise ValueError('Transformation generated invalid '
139
+ 'chunkstring:\n %s' % s)
140
+
141
+ if verify_tags<=0: return
142
+
143
+ tags1 = (re.split(r'[\{\}<>]+', s))[1:-1]
144
+ tags2 = [self._tag(piece) for piece in self._pieces]
145
+ if tags1 != tags2:
146
+ raise ValueError('Transformation generated invalid '
147
+ 'chunkstring: tag changed')
148
+
149
+ def to_chunkstruct(self, chunk_label='CHUNK'):
150
+ """
151
+ Return the chunk structure encoded by this ``ChunkString``.
152
+
153
+ :rtype: Tree
154
+ :raise ValueError: If a transformation has generated an
155
+ invalid chunkstring.
156
+ """
157
+ if self._debug > 0: self._verify(self._str, 1)
158
+
159
+ # Use this alternating list to create the chunkstruct.
160
+ pieces = []
161
+ index = 0
162
+ piece_in_chunk = 0
163
+ for piece in re.split('[{}]', self._str):
164
+
165
+ # Find the list of tokens contained in this piece.
166
+ length = piece.count('<')
167
+ subsequence = self._pieces[index:index+length]
168
+
169
+ # Add this list of tokens to our pieces.
170
+ if piece_in_chunk:
171
+ pieces.append(Tree(chunk_label, subsequence))
172
+ else:
173
+ pieces += subsequence
174
+
175
+ # Update index, piece_in_chunk
176
+ index += length
177
+ piece_in_chunk = not piece_in_chunk
178
+
179
+ return Tree(self._root_label, pieces)
180
+
181
+ def xform(self, regexp, repl):
182
+ """
183
+ Apply the given transformation to the string encoding of this
184
+ ``ChunkString``. In particular, find all occurrences that match
185
+ ``regexp``, and replace them using ``repl`` (as done by
186
+ ``re.sub``).
187
+
188
+ This transformation should only add and remove braces; it
189
+ should *not* modify the sequence of angle-bracket delimited
190
+ tags. Furthermore, this transformation may not result in
191
+ improper bracketing. Note, in particular, that bracketing may
192
+ not be nested.
193
+
194
+ :type regexp: str or regexp
195
+ :param regexp: A regular expression matching the substring
196
+ that should be replaced. This will typically include a
197
+ named group, which can be used by ``repl``.
198
+ :type repl: str
199
+ :param repl: An expression specifying what should replace the
200
+ matched substring. Typically, this will include a named
201
+ replacement group, specified by ``regexp``.
202
+ :rtype: None
203
+ :raise ValueError: If this transformation generated an
204
+ invalid chunkstring.
205
+ """
206
+ # Do the actual substitution
207
+ s = re.sub(regexp, repl, self._str)
208
+
209
+ # The substitution might have generated "empty chunks"
210
+ # (substrings of the form "{}"). Remove them, so they don't
211
+ # interfere with other transformations.
212
+ s = re.sub('\{\}', '', s)
213
+
214
+ # Make sure that the transformation was legal.
215
+ if self._debug > 1: self._verify(s, self._debug-2)
216
+
217
+ # Commit the transformation.
218
+ self._str = s
219
+
220
+ def __repr__(self):
221
+ """
222
+ Return a string representation of this ``ChunkString``.
223
+ It has the form::
224
+
225
+ <ChunkString: '{<DT><JJ><NN>}<VBN><IN>{<DT><NN>}'>
226
+
227
+ :rtype: str
228
+ """
229
+ return '<ChunkString: %s>' % unicode_repr(self._str)
230
+
231
+ def __str__(self):
232
+ """
233
+ Return a formatted representation of this ``ChunkString``.
234
+ This representation will include extra spaces to ensure that
235
+ tags will line up with the representation of other
236
+ ``ChunkStrings`` for the same text, regardless of the chunking.
237
+
238
+ :rtype: str
239
+ """
240
+ # Add spaces to make everything line up.
241
+ str = re.sub(r'>(?!\})', r'> ', self._str)
242
+ str = re.sub(r'([^\{])<', r'\1 <', str)
243
+ if str[0] == '<': str = ' ' + str
244
+ return str
245
+
246
+ ##//////////////////////////////////////////////////////
247
+ ## Chunking Rules
248
+ ##//////////////////////////////////////////////////////
249
+
250
+ @python_2_unicode_compatible
251
+ class RegexpChunkRule(object):
252
+ """
253
+ A rule specifying how to modify the chunking in a ``ChunkString``,
254
+ using a transformational regular expression. The
255
+ ``RegexpChunkRule`` class itself can be used to implement any
256
+ transformational rule based on regular expressions. There are
257
+ also a number of subclasses, which can be used to implement
258
+ simpler types of rules, based on matching regular expressions.
259
+
260
+ Each ``RegexpChunkRule`` has a regular expression and a
261
+ replacement expression. When a ``RegexpChunkRule`` is "applied"
262
+ to a ``ChunkString``, it searches the ``ChunkString`` for any
263
+ substring that matches the regular expression, and replaces it
264
+ using the replacement expression. This search/replace operation
265
+ has the same semantics as ``re.sub``.
266
+
267
+ Each ``RegexpChunkRule`` also has a description string, which
268
+ gives a short (typically less than 75 characters) description of
269
+ the purpose of the rule.
270
+
271
+ This transformation defined by this ``RegexpChunkRule`` should
272
+ only add and remove braces; it should *not* modify the sequence
273
+ of angle-bracket delimited tags. Furthermore, this transformation
274
+ may not result in nested or mismatched bracketing.
275
+ """
276
+ def __init__(self, regexp, repl, descr):
277
+ """
278
+ Construct a new RegexpChunkRule.
279
+
280
+ :type regexp: regexp or str
281
+ :param regexp: The regular expression for this ``RegexpChunkRule``.
282
+ When this rule is applied to a ``ChunkString``, any
283
+ substring that matches ``regexp`` will be replaced using
284
+ the replacement string ``repl``. Note that this must be a
285
+ normal regular expression, not a tag pattern.
286
+ :type repl: str
287
+ :param repl: The replacement expression for this ``RegexpChunkRule``.
288
+ When this rule is applied to a ``ChunkString``, any substring
289
+ that matches ``regexp`` will be replaced using ``repl``.
290
+ :type descr: str
291
+ :param descr: A short description of the purpose and/or effect
292
+ of this rule.
293
+ """
294
+ if isinstance(regexp, string_types):
295
+ regexp = re.compile(regexp)
296
+ self._repl = repl
297
+ self._descr = descr
298
+ self._regexp = regexp
299
+
300
+ def apply(self, chunkstr):
301
+ # Keep docstring generic so we can inherit it.
302
+ """
303
+ Apply this rule to the given ``ChunkString``. See the
304
+ class reference documentation for a description of what it
305
+ means to apply a rule.
306
+
307
+ :type chunkstr: ChunkString
308
+ :param chunkstr: The chunkstring to which this rule is applied.
309
+ :rtype: None
310
+ :raise ValueError: If this transformation generated an
311
+ invalid chunkstring.
312
+ """
313
+ chunkstr.xform(self._regexp, self._repl)
314
+
315
+ def descr(self):
316
+ """
317
+ Return a short description of the purpose and/or effect of
318
+ this rule.
319
+
320
+ :rtype: str
321
+ """
322
+ return self._descr
323
+
324
+ def __repr__(self):
325
+ """
326
+ Return a string representation of this rule. It has the form::
327
+
328
+ <RegexpChunkRule: '{<IN|VB.*>}'->'<IN>'>
329
+
330
+ Note that this representation does not include the
331
+ description string; that string can be accessed
332
+ separately with the ``descr()`` method.
333
+
334
+ :rtype: str
335
+ """
336
+ return ('<RegexpChunkRule: '+unicode_repr(self._regexp.pattern)+
337
+ '->'+unicode_repr(self._repl)+'>')
338
+
339
+ @staticmethod
340
+ def fromstring(s):
341
+ """
342
+ Create a RegexpChunkRule from a string description.
343
+ Currently, the following formats are supported::
344
+
345
+ {regexp} # chunk rule
346
+ }regexp{ # chink rule
347
+ regexp}{regexp # split rule
348
+ regexp{}regexp # merge rule
349
+
350
+ Where ``regexp`` is a regular expression for the rule. Any
351
+ text following the comment marker (``#``) will be used as
352
+ the rule's description:
353
+
354
+ >>> from nltk.chunk.regexp import RegexpChunkRule
355
+ >>> RegexpChunkRule.fromstring('{<DT>?<NN.*>+}')
356
+ <ChunkRule: '<DT>?<NN.*>+'>
357
+ """
358
+ # Split off the comment (but don't split on '\#')
359
+ m = re.match(r'(?P<rule>(\\.|[^#])*)(?P<comment>#.*)?', s)
360
+ rule = m.group('rule').strip()
361
+ comment = (m.group('comment') or '')[1:].strip()
362
+
363
+ # Pattern bodies: chunk, chink, split, merge
364
+ try:
365
+ if not rule:
366
+ raise ValueError('Empty chunk pattern')
367
+ if rule[0] == '{' and rule[-1] == '}':
368
+ return ChunkRule(rule[1:-1], comment)
369
+ elif rule[0] == '}' and rule[-1] == '{':
370
+ return ChinkRule(rule[1:-1], comment)
371
+ elif '}{' in rule:
372
+ left, right = rule.split('}{')
373
+ return SplitRule(left, right, comment)
374
+ elif '{}' in rule:
375
+ left, right = rule.split('{}')
376
+ return MergeRule(left, right, comment)
377
+ elif re.match('[^{}]*{[^{}]*}[^{}]*', rule):
378
+ left, chunk, right = re.split('[{}]', rule)
379
+ return ChunkRuleWithContext(left, chunk, right, comment)
380
+ else:
381
+ raise ValueError('Illegal chunk pattern: %s' % rule)
382
+ except (ValueError, re.error):
383
+ raise ValueError('Illegal chunk pattern: %s' % rule)
384
+
385
+
386
+ @python_2_unicode_compatible
387
+ class ChunkRule(RegexpChunkRule):
388
+ """
389
+ A rule specifying how to add chunks to a ``ChunkString``, using a
390
+ matching tag pattern. When applied to a ``ChunkString``, it will
391
+ find any substring that matches this tag pattern and that is not
392
+ already part of a chunk, and create a new chunk containing that
393
+ substring.
394
+ """
395
+ def __init__(self, tag_pattern, descr):
396
+
397
+ """
398
+ Construct a new ``ChunkRule``.
399
+
400
+ :type tag_pattern: str
401
+ :param tag_pattern: This rule's tag pattern. When
402
+ applied to a ``ChunkString``, this rule will
403
+ chunk any substring that matches this tag pattern and that
404
+ is not already part of a chunk.
405
+ :type descr: str
406
+ :param descr: A short description of the purpose and/or effect
407
+ of this rule.
408
+ """
409
+ self._pattern = tag_pattern
410
+ regexp = re.compile('(?P<chunk>%s)%s' %
411
+ (tag_pattern2re_pattern(tag_pattern),
412
+ ChunkString.IN_CHINK_PATTERN))
413
+ RegexpChunkRule.__init__(self, regexp, '{\g<chunk>}', descr)
414
+
415
+ def __repr__(self):
416
+ """
417
+ Return a string representation of this rule. It has the form::
418
+
419
+ <ChunkRule: '<IN|VB.*>'>
420
+
421
+ Note that this representation does not include the
422
+ description string; that string can be accessed
423
+ separately with the ``descr()`` method.
424
+
425
+ :rtype: str
426
+ """
427
+ return '<ChunkRule: '+unicode_repr(self._pattern)+'>'
428
+
429
+ @python_2_unicode_compatible
430
+ class ChinkRule(RegexpChunkRule):
431
+ """
432
+ A rule specifying how to remove chinks to a ``ChunkString``,
433
+ using a matching tag pattern. When applied to a
434
+ ``ChunkString``, it will find any substring that matches this
435
+ tag pattern and that is contained in a chunk, and remove it
436
+ from that chunk, thus creating two new chunks.
437
+ """
438
+ def __init__(self, tag_pattern, descr):
439
+ """
440
+ Construct a new ``ChinkRule``.
441
+
442
+ :type tag_pattern: str
443
+ :param tag_pattern: This rule's tag pattern. When
444
+ applied to a ``ChunkString``, this rule will
445
+ find any substring that matches this tag pattern and that
446
+ is contained in a chunk, and remove it from that chunk,
447
+ thus creating two new chunks.
448
+ :type descr: str
449
+ :param descr: A short description of the purpose and/or effect
450
+ of this rule.
451
+ """
452
+ self._pattern = tag_pattern
453
+ regexp = re.compile('(?P<chink>%s)%s' %
454
+ (tag_pattern2re_pattern(tag_pattern),
455
+ ChunkString.IN_CHUNK_PATTERN))
456
+ RegexpChunkRule.__init__(self, regexp, '}\g<chink>{', descr)
457
+
458
+ def __repr__(self):
459
+ """
460
+ Return a string representation of this rule. It has the form::
461
+
462
+ <ChinkRule: '<IN|VB.*>'>
463
+
464
+ Note that this representation does not include the
465
+ description string; that string can be accessed
466
+ separately with the ``descr()`` method.
467
+
468
+ :rtype: str
469
+ """
470
+ return '<ChinkRule: '+unicode_repr(self._pattern)+'>'
471
+
472
+
473
+ @python_2_unicode_compatible
474
+ class UnChunkRule(RegexpChunkRule):
475
+ """
476
+ A rule specifying how to remove chunks to a ``ChunkString``,
477
+ using a matching tag pattern. When applied to a
478
+ ``ChunkString``, it will find any complete chunk that matches this
479
+ tag pattern, and un-chunk it.
480
+ """
481
+ def __init__(self, tag_pattern, descr):
482
+ """
483
+ Construct a new ``UnChunkRule``.
484
+
485
+ :type tag_pattern: str
486
+ :param tag_pattern: This rule's tag pattern. When
487
+ applied to a ``ChunkString``, this rule will
488
+ find any complete chunk that matches this tag pattern,
489
+ and un-chunk it.
490
+ :type descr: str
491
+ :param descr: A short description of the purpose and/or effect
492
+ of this rule.
493
+ """
494
+ self._pattern = tag_pattern
495
+ regexp = re.compile('\{(?P<chunk>%s)\}' %
496
+ tag_pattern2re_pattern(tag_pattern))
497
+ RegexpChunkRule.__init__(self, regexp, '\g<chunk>', descr)
498
+
499
+ def __repr__(self):
500
+ """
501
+ Return a string representation of this rule. It has the form::
502
+
503
+ <UnChunkRule: '<IN|VB.*>'>
504
+
505
+ Note that this representation does not include the
506
+ description string; that string can be accessed
507
+ separately with the ``descr()`` method.
508
+
509
+ :rtype: str
510
+ """
511
+ return '<UnChunkRule: '+unicode_repr(self._pattern)+'>'
512
+
513
+
514
+ @python_2_unicode_compatible
515
+ class MergeRule(RegexpChunkRule):
516
+ """
517
+ A rule specifying how to merge chunks in a ``ChunkString``, using
518
+ two matching tag patterns: a left pattern, and a right pattern.
519
+ When applied to a ``ChunkString``, it will find any chunk whose end
520
+ matches left pattern, and immediately followed by a chunk whose
521
+ beginning matches right pattern. It will then merge those two
522
+ chunks into a single chunk.
523
+ """
524
+ def __init__(self, left_tag_pattern, right_tag_pattern, descr):
525
+ """
526
+ Construct a new ``MergeRule``.
527
+
528
+ :type right_tag_pattern: str
529
+ :param right_tag_pattern: This rule's right tag
530
+ pattern. When applied to a ``ChunkString``, this
531
+ rule will find any chunk whose end matches
532
+ ``left_tag_pattern``, and immediately followed by a chunk
533
+ whose beginning matches this pattern. It will
534
+ then merge those two chunks into a single chunk.
535
+ :type left_tag_pattern: str
536
+ :param left_tag_pattern: This rule's left tag
537
+ pattern. When applied to a ``ChunkString``, this
538
+ rule will find any chunk whose end matches
539
+ this pattern, and immediately followed by a chunk
540
+ whose beginning matches ``right_tag_pattern``. It will
541
+ then merge those two chunks into a single chunk.
542
+
543
+ :type descr: str
544
+ :param descr: A short description of the purpose and/or effect
545
+ of this rule.
546
+ """
547
+ # Ensure that the individual patterns are coherent. E.g., if
548
+ # left='(' and right=')', then this will raise an exception:
549
+ re.compile(tag_pattern2re_pattern(left_tag_pattern))
550
+ re.compile(tag_pattern2re_pattern(right_tag_pattern))
551
+
552
+ self._left_tag_pattern = left_tag_pattern
553
+ self._right_tag_pattern = right_tag_pattern
554
+ regexp = re.compile('(?P<left>%s)}{(?=%s)' %
555
+ (tag_pattern2re_pattern(left_tag_pattern),
556
+ tag_pattern2re_pattern(right_tag_pattern)))
557
+ RegexpChunkRule.__init__(self, regexp, '\g<left>', descr)
558
+
559
+ def __repr__(self):
560
+ """
561
+ Return a string representation of this rule. It has the form::
562
+
563
+ <MergeRule: '<NN|DT|JJ>', '<NN|JJ>'>
564
+
565
+ Note that this representation does not include the
566
+ description string; that string can be accessed
567
+ separately with the ``descr()`` method.
568
+
569
+ :rtype: str
570
+ """
571
+ return ('<MergeRule: '+unicode_repr(self._left_tag_pattern)+', '+
572
+ unicode_repr(self._right_tag_pattern)+'>')
573
+
574
+
575
+ @python_2_unicode_compatible
576
+ class SplitRule(RegexpChunkRule):
577
+ """
578
+ A rule specifying how to split chunks in a ``ChunkString``, using
579
+ two matching tag patterns: a left pattern, and a right pattern.
580
+ When applied to a ``ChunkString``, it will find any chunk that
581
+ matches the left pattern followed by the right pattern. It will
582
+ then split the chunk into two new chunks, at the point between the
583
+ two pattern matches.
584
+ """
585
+ def __init__(self, left_tag_pattern, right_tag_pattern, descr):
586
+ """
587
+ Construct a new ``SplitRule``.
588
+
589
+ :type right_tag_pattern: str
590
+ :param right_tag_pattern: This rule's right tag
591
+ pattern. When applied to a ``ChunkString``, this rule will
592
+ find any chunk containing a substring that matches
593
+ ``left_tag_pattern`` followed by this pattern. It will
594
+ then split the chunk into two new chunks at the point
595
+ between these two matching patterns.
596
+ :type left_tag_pattern: str
597
+ :param left_tag_pattern: This rule's left tag
598
+ pattern. When applied to a ``ChunkString``, this rule will
599
+ find any chunk containing a substring that matches this
600
+ pattern followed by ``right_tag_pattern``. It will then
601
+ split the chunk into two new chunks at the point between
602
+ these two matching patterns.
603
+ :type descr: str
604
+ :param descr: A short description of the purpose and/or effect
605
+ of this rule.
606
+ """
607
+ # Ensure that the individual patterns are coherent. E.g., if
608
+ # left='(' and right=')', then this will raise an exception:
609
+ re.compile(tag_pattern2re_pattern(left_tag_pattern))
610
+ re.compile(tag_pattern2re_pattern(right_tag_pattern))
611
+
612
+ self._left_tag_pattern = left_tag_pattern
613
+ self._right_tag_pattern = right_tag_pattern
614
+ regexp = re.compile('(?P<left>%s)(?=%s)' %
615
+ (tag_pattern2re_pattern(left_tag_pattern),
616
+ tag_pattern2re_pattern(right_tag_pattern)))
617
+ RegexpChunkRule.__init__(self, regexp, r'\g<left>}{', descr)
618
+
619
+ def __repr__(self):
620
+ """
621
+ Return a string representation of this rule. It has the form::
622
+
623
+ <SplitRule: '<NN>', '<DT>'>
624
+
625
+ Note that this representation does not include the
626
+ description string; that string can be accessed
627
+ separately with the ``descr()`` method.
628
+
629
+ :rtype: str
630
+ """
631
+ return ('<SplitRule: '+unicode_repr(self._left_tag_pattern)+', '+
632
+ unicode_repr(self._right_tag_pattern)+'>')
633
+
634
+
635
+ @python_2_unicode_compatible
636
+ class ExpandLeftRule(RegexpChunkRule):
637
+ """
638
+ A rule specifying how to expand chunks in a ``ChunkString`` to the left,
639
+ using two matching tag patterns: a left pattern, and a right pattern.
640
+ When applied to a ``ChunkString``, it will find any chunk whose beginning
641
+ matches right pattern, and immediately preceded by a chink whose
642
+ end matches left pattern. It will then expand the chunk to incorporate
643
+ the new material on the left.
644
+ """
645
+ def __init__(self, left_tag_pattern, right_tag_pattern, descr):
646
+ """
647
+ Construct a new ``ExpandRightRule``.
648
+
649
+ :type right_tag_pattern: str
650
+ :param right_tag_pattern: This rule's right tag
651
+ pattern. When applied to a ``ChunkString``, this
652
+ rule will find any chunk whose beginning matches
653
+ ``right_tag_pattern``, and immediately preceded by a chink
654
+ whose end matches this pattern. It will
655
+ then merge those two chunks into a single chunk.
656
+ :type left_tag_pattern: str
657
+ :param left_tag_pattern: This rule's left tag
658
+ pattern. When applied to a ``ChunkString``, this
659
+ rule will find any chunk whose beginning matches
660
+ this pattern, and immediately preceded by a chink
661
+ whose end matches ``left_tag_pattern``. It will
662
+ then expand the chunk to incorporate the new material on the left.
663
+
664
+ :type descr: str
665
+ :param descr: A short description of the purpose and/or effect
666
+ of this rule.
667
+ """
668
+ # Ensure that the individual patterns are coherent. E.g., if
669
+ # left='(' and right=')', then this will raise an exception:
670
+ re.compile(tag_pattern2re_pattern(left_tag_pattern))
671
+ re.compile(tag_pattern2re_pattern(right_tag_pattern))
672
+
673
+ self._left_tag_pattern = left_tag_pattern
674
+ self._right_tag_pattern = right_tag_pattern
675
+ regexp = re.compile('(?P<left>%s)\{(?P<right>%s)' %
676
+ (tag_pattern2re_pattern(left_tag_pattern),
677
+ tag_pattern2re_pattern(right_tag_pattern)))
678
+ RegexpChunkRule.__init__(self, regexp, '{\g<left>\g<right>', descr)
679
+
680
+ def __repr__(self):
681
+ """
682
+ Return a string representation of this rule. It has the form::
683
+
684
+ <ExpandLeftRule: '<NN|DT|JJ>', '<NN|JJ>'>
685
+
686
+ Note that this representation does not include the
687
+ description string; that string can be accessed
688
+ separately with the ``descr()`` method.
689
+
690
+ :rtype: str
691
+ """
692
+ return ('<ExpandLeftRule: '+unicode_repr(self._left_tag_pattern)+', '+
693
+ unicode_repr(self._right_tag_pattern)+'>')
694
+
695
+
696
+ @python_2_unicode_compatible
697
+ class ExpandRightRule(RegexpChunkRule):
698
+ """
699
+ A rule specifying how to expand chunks in a ``ChunkString`` to the
700
+ right, using two matching tag patterns: a left pattern, and a
701
+ right pattern. When applied to a ``ChunkString``, it will find any
702
+ chunk whose end matches left pattern, and immediately followed by
703
+ a chink whose beginning matches right pattern. It will then
704
+ expand the chunk to incorporate the new material on the right.
705
+ """
706
+ def __init__(self, left_tag_pattern, right_tag_pattern, descr):
707
+ """
708
+ Construct a new ``ExpandRightRule``.
709
+
710
+ :type right_tag_pattern: str
711
+ :param right_tag_pattern: This rule's right tag
712
+ pattern. When applied to a ``ChunkString``, this
713
+ rule will find any chunk whose end matches
714
+ ``left_tag_pattern``, and immediately followed by a chink
715
+ whose beginning matches this pattern. It will
716
+ then merge those two chunks into a single chunk.
717
+ :type left_tag_pattern: str
718
+ :param left_tag_pattern: This rule's left tag
719
+ pattern. When applied to a ``ChunkString``, this
720
+ rule will find any chunk whose end matches
721
+ this pattern, and immediately followed by a chink
722
+ whose beginning matches ``right_tag_pattern``. It will
723
+ then expand the chunk to incorporate the new material on the right.
724
+
725
+ :type descr: str
726
+ :param descr: A short description of the purpose and/or effect
727
+ of this rule.
728
+ """
729
+ # Ensure that the individual patterns are coherent. E.g., if
730
+ # left='(' and right=')', then this will raise an exception:
731
+ re.compile(tag_pattern2re_pattern(left_tag_pattern))
732
+ re.compile(tag_pattern2re_pattern(right_tag_pattern))
733
+
734
+ self._left_tag_pattern = left_tag_pattern
735
+ self._right_tag_pattern = right_tag_pattern
736
+ regexp = re.compile('(?P<left>%s)\}(?P<right>%s)' %
737
+ (tag_pattern2re_pattern(left_tag_pattern),
738
+ tag_pattern2re_pattern(right_tag_pattern)))
739
+ RegexpChunkRule.__init__(self, regexp, '\g<left>\g<right>}', descr)
740
+
741
+ def __repr__(self):
742
+ """
743
+ Return a string representation of this rule. It has the form::
744
+
745
+ <ExpandRightRule: '<NN|DT|JJ>', '<NN|JJ>'>
746
+
747
+ Note that this representation does not include the
748
+ description string; that string can be accessed
749
+ separately with the ``descr()`` method.
750
+
751
+ :rtype: str
752
+ """
753
+ return ('<ExpandRightRule: '+unicode_repr(self._left_tag_pattern)+', '+
754
+ unicode_repr(self._right_tag_pattern)+'>')
755
+
756
+
757
+ @python_2_unicode_compatible
758
+ class ChunkRuleWithContext(RegexpChunkRule):
759
+ """
760
+ A rule specifying how to add chunks to a ``ChunkString``, using
761
+ three matching tag patterns: one for the left context, one for the
762
+ chunk, and one for the right context. When applied to a
763
+ ``ChunkString``, it will find any substring that matches the chunk
764
+ tag pattern, is surrounded by substrings that match the two
765
+ context patterns, and is not already part of a chunk; and create a
766
+ new chunk containing the substring that matched the chunk tag
767
+ pattern.
768
+
769
+ Caveat: Both the left and right context are consumed when this
770
+ rule matches; therefore, if you need to find overlapping matches,
771
+ you will need to apply your rule more than once.
772
+ """
773
+ def __init__(self, left_context_tag_pattern, chunk_tag_pattern,
774
+ right_context_tag_pattern, descr):
775
+ """
776
+ Construct a new ``ChunkRuleWithContext``.
777
+
778
+ :type left_context_tag_pattern: str
779
+ :param left_context_tag_pattern: A tag pattern that must match
780
+ the left context of ``chunk_tag_pattern`` for this rule to
781
+ apply.
782
+ :type chunk_tag_pattern: str
783
+ :param chunk_tag_pattern: A tag pattern that must match for this
784
+ rule to apply. If the rule does apply, then this pattern
785
+ also identifies the substring that will be made into a chunk.
786
+ :type right_context_tag_pattern: str
787
+ :param right_context_tag_pattern: A tag pattern that must match
788
+ the right context of ``chunk_tag_pattern`` for this rule to
789
+ apply.
790
+ :type descr: str
791
+ :param descr: A short description of the purpose and/or effect
792
+ of this rule.
793
+ """
794
+ # Ensure that the individual patterns are coherent. E.g., if
795
+ # left='(' and right=')', then this will raise an exception:
796
+ re.compile(tag_pattern2re_pattern(left_context_tag_pattern))
797
+ re.compile(tag_pattern2re_pattern(chunk_tag_pattern))
798
+ re.compile(tag_pattern2re_pattern(right_context_tag_pattern))
799
+
800
+ self._left_context_tag_pattern = left_context_tag_pattern
801
+ self._chunk_tag_pattern = chunk_tag_pattern
802
+ self._right_context_tag_pattern = right_context_tag_pattern
803
+ regexp = re.compile('(?P<left>%s)(?P<chunk>%s)(?P<right>%s)%s' %
804
+ (tag_pattern2re_pattern(left_context_tag_pattern),
805
+ tag_pattern2re_pattern(chunk_tag_pattern),
806
+ tag_pattern2re_pattern(right_context_tag_pattern),
807
+ ChunkString.IN_CHINK_PATTERN))
808
+ replacement = r'\g<left>{\g<chunk>}\g<right>'
809
+ RegexpChunkRule.__init__(self, regexp, replacement, descr)
810
+
811
+ def __repr__(self):
812
+ """
813
+ Return a string representation of this rule. It has the form::
814
+
815
+ <ChunkRuleWithContext: '<IN>', '<NN>', '<DT>'>
816
+
817
+ Note that this representation does not include the
818
+ description string; that string can be accessed
819
+ separately with the ``descr()`` method.
820
+
821
+ :rtype: str
822
+ """
823
+ return '<ChunkRuleWithContext: %r, %r, %r>' % (
824
+ self._left_context_tag_pattern, self._chunk_tag_pattern,
825
+ self._right_context_tag_pattern)
826
+
827
+ ##//////////////////////////////////////////////////////
828
+ ## Tag Pattern Format Conversion
829
+ ##//////////////////////////////////////////////////////
830
+
831
+ # this should probably be made more strict than it is -- e.g., it
832
+ # currently accepts 'foo'.
833
+ CHUNK_TAG_PATTERN = re.compile(r'^((%s|<%s>)*)$' %
834
+ ('([^\{\}<>]|\{\d+,?\}|\{\d*,\d+\})+',
835
+ '[^\{\}<>]+'))
836
+
837
+
838
+
839
+
840
+
841
+ def tag_pattern2re_pattern(tag_pattern):
842
+ """
843
+ Convert a tag pattern to a regular expression pattern. A "tag
844
+ pattern" is a modified version of a regular expression, designed
845
+ for matching sequences of tags. The differences between regular
846
+ expression patterns and tag patterns are:
847
+
848
+ - In tag patterns, ``'<'`` and ``'>'`` act as parentheses; so
849
+ ``'<NN>+'`` matches one or more repetitions of ``'<NN>'``, not
850
+ ``'<NN'`` followed by one or more repetitions of ``'>'``.
851
+ - Whitespace in tag patterns is ignored. So
852
+ ``'<DT> | <NN>'`` is equivalant to ``'<DT>|<NN>'``
853
+ - In tag patterns, ``'.'`` is equivalant to ``'[^{}<>]'``; so
854
+ ``'<NN.*>'`` matches any single tag starting with ``'NN'``.
855
+
856
+ In particular, ``tag_pattern2re_pattern`` performs the following
857
+ transformations on the given pattern:
858
+
859
+ - Replace '.' with '[^<>{}]'
860
+ - Remove any whitespace
861
+ - Add extra parens around '<' and '>', to make '<' and '>' act
862
+ like parentheses. E.g., so that in '<NN>+', the '+' has scope
863
+ over the entire '<NN>'; and so that in '<NN|IN>', the '|' has
864
+ scope over 'NN' and 'IN', but not '<' or '>'.
865
+ - Check to make sure the resulting pattern is valid.
866
+
867
+ :type tag_pattern: str
868
+ :param tag_pattern: The tag pattern to convert to a regular
869
+ expression pattern.
870
+ :raise ValueError: If ``tag_pattern`` is not a valid tag pattern.
871
+ In particular, ``tag_pattern`` should not include braces; and it
872
+ should not contain nested or mismatched angle-brackets.
873
+ :rtype: str
874
+ :return: A regular expression pattern corresponding to
875
+ ``tag_pattern``.
876
+ """
877
+ # Clean up the regular expression
878
+ tag_pattern = re.sub(r'\s', '', tag_pattern)
879
+ tag_pattern = re.sub(r'<', '(<(', tag_pattern)
880
+ tag_pattern = re.sub(r'>', ')>)', tag_pattern)
881
+
882
+ # Check the regular expression
883
+ if not CHUNK_TAG_PATTERN.match(tag_pattern):
884
+ raise ValueError('Bad tag pattern: %r' % tag_pattern)
885
+
886
+ # Replace "." with CHUNK_TAG_CHAR.
887
+ # We have to do this after, since it adds {}[]<>s, which would
888
+ # confuse CHUNK_TAG_PATTERN.
889
+ # PRE doesn't have lookback assertions, so reverse twice, and do
890
+ # the pattern backwards (with lookahead assertions). This can be
891
+ # made much cleaner once we can switch back to SRE.
892
+ def reverse_str(str):
893
+ lst = list(str)
894
+ lst.reverse()
895
+ return ''.join(lst)
896
+ tc_rev = reverse_str(ChunkString.CHUNK_TAG_CHAR)
897
+ reversed = reverse_str(tag_pattern)
898
+ reversed = re.sub(r'\.(?!\\(\\\\)*($|[^\\]))', tc_rev, reversed)
899
+ tag_pattern = reverse_str(reversed)
900
+
901
+ return tag_pattern
902
+
903
+
904
+ ##//////////////////////////////////////////////////////
905
+ ## RegexpChunkParser
906
+ ##//////////////////////////////////////////////////////
907
+
908
+ @python_2_unicode_compatible
909
+ class RegexpChunkParser(ChunkParserI):
910
+ """
911
+ A regular expression based chunk parser. ``RegexpChunkParser`` uses a
912
+ sequence of "rules" to find chunks of a single type within a
913
+ text. The chunking of the text is encoded using a ``ChunkString``,
914
+ and each rule acts by modifying the chunking in the
915
+ ``ChunkString``. The rules are all implemented using regular
916
+ expression matching and substitution.
917
+
918
+ The ``RegexpChunkRule`` class and its subclasses (``ChunkRule``,
919
+ ``ChinkRule``, ``UnChunkRule``, ``MergeRule``, and ``SplitRule``)
920
+ define the rules that are used by ``RegexpChunkParser``. Each rule
921
+ defines an ``apply()`` method, which modifies the chunking encoded
922
+ by a given ``ChunkString``.
923
+
924
+ :type _rules: list(RegexpChunkRule)
925
+ :ivar _rules: The list of rules that should be applied to a text.
926
+ :type _trace: int
927
+ :ivar _trace: The default level of tracing.
928
+
929
+ """
930
+ def __init__(self, rules, chunk_label='NP', root_label='S', trace=0):
931
+ """
932
+ Construct a new ``RegexpChunkParser``.
933
+
934
+ :type rules: list(RegexpChunkRule)
935
+ :param rules: The sequence of rules that should be used to
936
+ generate the chunking for a tagged text.
937
+ :type chunk_label: str
938
+ :param chunk_label: The node value that should be used for
939
+ chunk subtrees. This is typically a short string
940
+ describing the type of information contained by the chunk,
941
+ such as ``"NP"`` for base noun phrases.
942
+ :type root_label: str
943
+ :param root_label: The node value that should be used for the
944
+ top node of the chunk structure.
945
+ :type trace: int
946
+ :param trace: The level of tracing that should be used when
947
+ parsing a text. ``0`` will generate no tracing output;
948
+ ``1`` will generate normal tracing output; and ``2`` or
949
+ higher will generate verbose tracing output.
950
+ """
951
+ self._rules = rules
952
+ self._trace = trace
953
+ self._chunk_label = chunk_label
954
+ self._root_label = root_label
955
+
956
+ def _trace_apply(self, chunkstr, verbose):
957
+ """
958
+ Apply each rule of this ``RegexpChunkParser`` to ``chunkstr``, in
959
+ turn. Generate trace output between each rule. If ``verbose``
960
+ is true, then generate verbose output.
961
+
962
+ :type chunkstr: ChunkString
963
+ :param chunkstr: The chunk string to which each rule should be
964
+ applied.
965
+ :type verbose: bool
966
+ :param verbose: Whether output should be verbose.
967
+ :rtype: None
968
+ """
969
+ print('# Input:')
970
+ print(chunkstr)
971
+ for rule in self._rules:
972
+ rule.apply(chunkstr)
973
+ if verbose:
974
+ print('#', rule.descr()+' ('+unicode_repr(rule)+'):')
975
+ else:
976
+ print('#', rule.descr()+':')
977
+ print(chunkstr)
978
+
979
+ def _notrace_apply(self, chunkstr):
980
+ """
981
+ Apply each rule of this ``RegexpChunkParser`` to ``chunkstr``, in
982
+ turn.
983
+
984
+ :param chunkstr: The chunk string to which each rule should be
985
+ applied.
986
+ :type chunkstr: ChunkString
987
+ :rtype: None
988
+ """
989
+
990
+ for rule in self._rules:
991
+ rule.apply(chunkstr)
992
+
993
+ def parse(self, chunk_struct, trace=None):
994
+ """
995
+ :type chunk_struct: Tree
996
+ :param chunk_struct: the chunk structure to be (further) chunked
997
+ :type trace: int
998
+ :param trace: The level of tracing that should be used when
999
+ parsing a text. ``0`` will generate no tracing output;
1000
+ ``1`` will generate normal tracing output; and ``2`` or
1001
+ highter will generate verbose tracing output. This value
1002
+ overrides the trace level value that was given to the
1003
+ constructor.
1004
+ :rtype: Tree
1005
+ :return: a chunk structure that encodes the chunks in a given
1006
+ tagged sentence. A chunk is a non-overlapping linguistic
1007
+ group, such as a noun phrase. The set of chunks
1008
+ identified in the chunk structure depends on the rules
1009
+ used to define this ``RegexpChunkParser``.
1010
+ """
1011
+ if len(chunk_struct) == 0:
1012
+ print('Warning: parsing empty text')
1013
+ return Tree(self._root_label, [])
1014
+
1015
+ try:
1016
+ chunk_struct.label()
1017
+ except AttributeError:
1018
+ chunk_struct = Tree(self._root_label, chunk_struct)
1019
+
1020
+ # Use the default trace value?
1021
+ if trace is None: trace = self._trace
1022
+
1023
+ chunkstr = ChunkString(chunk_struct)
1024
+
1025
+ # Apply the sequence of rules to the chunkstring.
1026
+ if trace:
1027
+ verbose = (trace>1)
1028
+ self._trace_apply(chunkstr, verbose)
1029
+ else:
1030
+ self._notrace_apply(chunkstr)
1031
+
1032
+ # Use the chunkstring to create a chunk structure.
1033
+ return chunkstr.to_chunkstruct(self._chunk_label)
1034
+
1035
+ def rules(self):
1036
+ """
1037
+ :return: the sequence of rules used by ``RegexpChunkParser``.
1038
+ :rtype: list(RegexpChunkRule)
1039
+ """
1040
+ return self._rules
1041
+
1042
+ def __repr__(self):
1043
+ """
1044
+ :return: a concise string representation of this
1045
+ ``RegexpChunkParser``.
1046
+ :rtype: str
1047
+ """
1048
+ return "<RegexpChunkParser with %d rules>" % len(self._rules)
1049
+
1050
+ def __str__(self):
1051
+ """
1052
+ :return: a verbose string representation of this ``RegexpChunkParser``.
1053
+ :rtype: str
1054
+ """
1055
+ s = "RegexpChunkParser with %d rules:\n" % len(self._rules)
1056
+ margin = 0
1057
+ for rule in self._rules:
1058
+ margin = max(margin, len(rule.descr()))
1059
+ if margin < 35:
1060
+ format = " %" + repr(-(margin+3)) + "s%s\n"
1061
+ else:
1062
+ format = " %s\n %s\n"
1063
+ for rule in self._rules:
1064
+ s += format % (rule.descr(), unicode_repr(rule))
1065
+ return s[:-1]
1066
+
1067
+ ##//////////////////////////////////////////////////////
1068
+ ## Chunk Grammar
1069
+ ##//////////////////////////////////////////////////////
1070
+
1071
+ @python_2_unicode_compatible
1072
+ class RegexpParser(ChunkParserI):
1073
+ """
1074
+ A grammar based chunk parser. ``chunk.RegexpParser`` uses a set of
1075
+ regular expression patterns to specify the behavior of the parser.
1076
+ The chunking of the text is encoded using a ``ChunkString``, and
1077
+ each rule acts by modifying the chunking in the ``ChunkString``.
1078
+ The rules are all implemented using regular expression matching
1079
+ and substitution.
1080
+
1081
+ A grammar contains one or more clauses in the following form::
1082
+
1083
+ NP:
1084
+ {<DT|JJ>} # chunk determiners and adjectives
1085
+ }<[\.VI].*>+{ # chink any tag beginning with V, I, or .
1086
+ <.*>}{<DT> # split a chunk at a determiner
1087
+ <DT|JJ>{}<NN.*> # merge chunk ending with det/adj
1088
+ # with one starting with a noun
1089
+
1090
+ The patterns of a clause are executed in order. An earlier
1091
+ pattern may introduce a chunk boundary that prevents a later
1092
+ pattern from executing. Sometimes an individual pattern will
1093
+ match on multiple, overlapping extents of the input. As with
1094
+ regular expression substitution more generally, the chunker will
1095
+ identify the first match possible, then continue looking for matches
1096
+ after this one has ended.
1097
+
1098
+ The clauses of a grammar are also executed in order. A cascaded
1099
+ chunk parser is one having more than one clause. The maximum depth
1100
+ of a parse tree created by this chunk parser is the same as the
1101
+ number of clauses in the grammar.
1102
+
1103
+ When tracing is turned on, the comment portion of a line is displayed
1104
+ each time the corresponding pattern is applied.
1105
+
1106
+ :type _start: str
1107
+ :ivar _start: The start symbol of the grammar (the root node of
1108
+ resulting trees)
1109
+ :type _stages: int
1110
+ :ivar _stages: The list of parsing stages corresponding to the grammar
1111
+
1112
+ """
1113
+ def __init__(self, grammar, root_label='S', loop=1, trace=0):
1114
+ """
1115
+ Create a new chunk parser, from the given start state
1116
+ and set of chunk patterns.
1117
+
1118
+ :param grammar: The grammar, or a list of RegexpChunkParser objects
1119
+ :type grammar: str or list(RegexpChunkParser)
1120
+ :param root_label: The top node of the tree being created
1121
+ :type root_label: str or Nonterminal
1122
+ :param loop: The number of times to run through the patterns
1123
+ :type loop: int
1124
+ :type trace: int
1125
+ :param trace: The level of tracing that should be used when
1126
+ parsing a text. ``0`` will generate no tracing output;
1127
+ ``1`` will generate normal tracing output; and ``2`` or
1128
+ higher will generate verbose tracing output.
1129
+ """
1130
+ self._trace = trace
1131
+ self._stages = []
1132
+ self._grammar = grammar
1133
+ self._loop = loop
1134
+
1135
+ if isinstance(grammar, string_types):
1136
+ self._read_grammar(grammar, root_label, trace)
1137
+ else:
1138
+ # Make sur the grammar looks like it has the right type:
1139
+ type_err = ('Expected string or list of RegexpChunkParsers '
1140
+ 'for the grammar.')
1141
+ try: grammar = list(grammar)
1142
+ except: raise TypeError(type_err)
1143
+ for elt in grammar:
1144
+ if not isinstance(elt, RegexpChunkParser):
1145
+ raise TypeError(type_err)
1146
+ self._stages = grammar
1147
+
1148
+ def _read_grammar(self, grammar, root_label, trace):
1149
+ """
1150
+ Helper function for __init__: read the grammar if it is a
1151
+ string.
1152
+ """
1153
+ rules = []
1154
+ lhs = None
1155
+ for line in grammar.split('\n'):
1156
+ line = line.strip()
1157
+
1158
+ # New stage begins if there's an unescaped ':'
1159
+ m = re.match('(?P<nonterminal>(\\.|[^:])*)(:(?P<rule>.*))', line)
1160
+ if m:
1161
+ # Record the stage that we just completed.
1162
+ self._add_stage(rules, lhs, root_label, trace)
1163
+ # Start a new stage.
1164
+ lhs = m.group('nonterminal').strip()
1165
+ rules = []
1166
+ line = m.group('rule').strip()
1167
+
1168
+ # Skip blank & comment-only lines
1169
+ if line=='' or line.startswith('#'): continue
1170
+
1171
+ # Add the rule
1172
+ rules.append(RegexpChunkRule.fromstring(line))
1173
+
1174
+ # Record the final stage
1175
+ self._add_stage(rules, lhs, root_label, trace)
1176
+
1177
+ def _add_stage(self, rules, lhs, root_label, trace):
1178
+ """
1179
+ Helper function for __init__: add a new stage to the parser.
1180
+ """
1181
+ if rules != []:
1182
+ if not lhs:
1183
+ raise ValueError('Expected stage marker (eg NP:)')
1184
+ parser = RegexpChunkParser(rules, chunk_label=lhs,
1185
+ root_label=root_label, trace=trace)
1186
+ self._stages.append(parser)
1187
+
1188
+ def parse(self, chunk_struct, trace=None):
1189
+ """
1190
+ Apply the chunk parser to this input.
1191
+
1192
+ :type chunk_struct: Tree
1193
+ :param chunk_struct: the chunk structure to be (further) chunked
1194
+ (this tree is modified, and is also returned)
1195
+ :type trace: int
1196
+ :param trace: The level of tracing that should be used when
1197
+ parsing a text. ``0`` will generate no tracing output;
1198
+ ``1`` will generate normal tracing output; and ``2`` or
1199
+ highter will generate verbose tracing output. This value
1200
+ overrides the trace level value that was given to the
1201
+ constructor.
1202
+ :return: the chunked output.
1203
+ :rtype: Tree
1204
+ """
1205
+ if trace is None: trace = self._trace
1206
+ for i in range(self._loop):
1207
+ for parser in self._stages:
1208
+ chunk_struct = parser.parse(chunk_struct, trace=trace)
1209
+ return chunk_struct
1210
+
1211
+ def __repr__(self):
1212
+ """
1213
+ :return: a concise string representation of this ``chunk.RegexpParser``.
1214
+ :rtype: str
1215
+ """
1216
+ return "<chunk.RegexpParser with %d stages>" % len(self._stages)
1217
+
1218
+ def __str__(self):
1219
+ """
1220
+ :return: a verbose string representation of this
1221
+ ``RegexpParser``.
1222
+ :rtype: str
1223
+ """
1224
+ s = "chunk.RegexpParser with %d stages:\n" % len(self._stages)
1225
+ margin = 0
1226
+ for parser in self._stages:
1227
+ s += "%s\n" % parser
1228
+ return s[:-1]
1229
+
1230
+ ##//////////////////////////////////////////////////////
1231
+ ## Demonstration code
1232
+ ##//////////////////////////////////////////////////////
1233
+
1234
+ def demo_eval(chunkparser, text):
1235
+ """
1236
+ Demonstration code for evaluating a chunk parser, using a
1237
+ ``ChunkScore``. This function assumes that ``text`` contains one
1238
+ sentence per line, and that each sentence has the form expected by
1239
+ ``tree.chunk``. It runs the given chunk parser on each sentence in
1240
+ the text, and scores the result. It prints the final score
1241
+ (precision, recall, and f-measure); and reports the set of chunks
1242
+ that were missed and the set of chunks that were incorrect. (At
1243
+ most 10 missing chunks and 10 incorrect chunks are reported).
1244
+
1245
+ :param chunkparser: The chunkparser to be tested
1246
+ :type chunkparser: ChunkParserI
1247
+ :param text: The chunked tagged text that should be used for
1248
+ evaluation.
1249
+ :type text: str
1250
+ """
1251
+ from nltk import chunk
1252
+ from nltk.tree import Tree
1253
+
1254
+ # Evaluate our chunk parser.
1255
+ chunkscore = chunk.ChunkScore()
1256
+
1257
+ for sentence in text.split('\n'):
1258
+ print(sentence)
1259
+ sentence = sentence.strip()
1260
+ if not sentence: continue
1261
+ gold = chunk.tagstr2tree(sentence)
1262
+ tokens = gold.leaves()
1263
+ test = chunkparser.parse(Tree('S', tokens), trace=1)
1264
+ chunkscore.score(gold, test)
1265
+ print()
1266
+
1267
+ print('/'+('='*75)+'\\')
1268
+ print('Scoring', chunkparser)
1269
+ print(('-'*77))
1270
+ print('Precision: %5.1f%%' % (chunkscore.precision()*100), ' '*4, end=' ')
1271
+ print('Recall: %5.1f%%' % (chunkscore.recall()*100), ' '*6, end=' ')
1272
+ print('F-Measure: %5.1f%%' % (chunkscore.f_measure()*100))
1273
+
1274
+
1275
+ # Missed chunks.
1276
+ if chunkscore.missed():
1277
+ print('Missed:')
1278
+ missed = chunkscore.missed()
1279
+ for chunk in missed[:10]:
1280
+ print(' ', ' '.join(map(str,chunk)))
1281
+ if len(chunkscore.missed()) > 10:
1282
+ print(' ...')
1283
+
1284
+ # Incorrect chunks.
1285
+ if chunkscore.incorrect():
1286
+ print('Incorrect:')
1287
+ incorrect = chunkscore.incorrect()
1288
+ for chunk in incorrect[:10]:
1289
+ print(' ', ' '.join(map(str,chunk)))
1290
+ if len(chunkscore.incorrect()) > 10:
1291
+ print(' ...')
1292
+
1293
+ print('\\'+('='*75)+'/')
1294
+ print()
1295
+
1296
+ def demo():
1297
+ """
1298
+ A demonstration for the ``RegexpChunkParser`` class. A single text is
1299
+ parsed with four different chunk parsers, using a variety of rules
1300
+ and strategies.
1301
+ """
1302
+
1303
+ from nltk import chunk, Tree
1304
+
1305
+ text = """\
1306
+ [ the/DT little/JJ cat/NN ] sat/VBD on/IN [ the/DT mat/NN ] ./.
1307
+ [ John/NNP ] saw/VBD [the/DT cats/NNS] [the/DT dog/NN] chased/VBD ./.
1308
+ [ John/NNP ] thinks/VBZ [ Mary/NN ] saw/VBD [ the/DT cat/NN ] sit/VB on/IN [ the/DT mat/NN ]./.
1309
+ """
1310
+
1311
+ print('*'*75)
1312
+ print('Evaluation text:')
1313
+ print(text)
1314
+ print('*'*75)
1315
+ print()
1316
+
1317
+ grammar = r"""
1318
+ NP: # NP stage
1319
+ {<DT>?<JJ>*<NN>} # chunk determiners, adjectives and nouns
1320
+ {<NNP>+} # chunk proper nouns
1321
+ """
1322
+ cp = chunk.RegexpParser(grammar)
1323
+ demo_eval(cp, text)
1324
+
1325
+ grammar = r"""
1326
+ NP:
1327
+ {<.*>} # start by chunking each tag
1328
+ }<[\.VI].*>+{ # unchunk any verbs, prepositions or periods
1329
+ <DT|JJ>{}<NN.*> # merge det/adj with nouns
1330
+ """
1331
+ cp = chunk.RegexpParser(grammar)
1332
+ demo_eval(cp, text)
1333
+
1334
+ grammar = r"""
1335
+ NP: {<DT>?<JJ>*<NN>} # chunk determiners, adjectives and nouns
1336
+ VP: {<TO>?<VB.*>} # VP = verb words
1337
+ """
1338
+ cp = chunk.RegexpParser(grammar)
1339
+ demo_eval(cp, text)
1340
+
1341
+ grammar = r"""
1342
+ NP: {<.*>*} # start by chunking everything
1343
+ }<[\.VI].*>+{ # chink any verbs, prepositions or periods
1344
+ <.*>}{<DT> # separate on determiners
1345
+ PP: {<IN><NP>} # PP = preposition + noun phrase
1346
+ VP: {<VB.*><NP|PP>*} # VP = verb words + NPs and PPs
1347
+ """
1348
+ cp = chunk.RegexpParser(grammar)
1349
+ demo_eval(cp, text)
1350
+
1351
+ # Evaluation
1352
+
1353
+ from nltk.corpus import conll2000
1354
+
1355
+ print()
1356
+ print("Demonstration of empty grammar:")
1357
+
1358
+ cp = chunk.RegexpParser("")
1359
+ print(chunk.accuracy(cp, conll2000.chunked_sents('test.txt',
1360
+ chunk_types=('NP',))))
1361
+
1362
+ print()
1363
+ print("Demonstration of accuracy evaluation using CoNLL tags:")
1364
+
1365
+ grammar = r"""
1366
+ NP:
1367
+ {<.*>} # start by chunking each tag
1368
+ }<[\.VI].*>+{ # unchunk any verbs, prepositions or periods
1369
+ <DT|JJ>{}<NN.*> # merge det/adj with nouns
1370
+ """
1371
+ cp = chunk.RegexpParser(grammar)
1372
+ print(chunk.accuracy(cp, conll2000.chunked_sents('test.txt')[:5]))
1373
+
1374
+ print()
1375
+ print("Demonstration of tagged token input")
1376
+
1377
+ grammar = r"""
1378
+ NP: {<.*>*} # start by chunking everything
1379
+ }<[\.VI].*>+{ # chink any verbs, prepositions or periods
1380
+ <.*>}{<DT> # separate on determiners
1381
+ PP: {<IN><NP>} # PP = preposition + noun phrase
1382
+ VP: {<VB.*><NP|PP>*} # VP = verb words + NPs and PPs
1383
+ """
1384
+ cp = chunk.RegexpParser(grammar)
1385
+ print(cp.parse([("the","DT"), ("little","JJ"), ("cat", "NN"),
1386
+ ("sat", "VBD"), ("on", "IN"), ("the", "DT"),
1387
+ ("mat", "NN"), (".", ".")]))
1388
+
1389
+ if __name__ == '__main__':
1390
+ demo()