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.
- {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
- {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
- {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
- {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
- nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
- nltk-3.3/nltk/VERSION +1 -0
- {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
- {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
- {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
- {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
- {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
- {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
- {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
- {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
- {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
- {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
- {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
- {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
- {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
- {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
- {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
- {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
- nltk-3.3/nltk/ccg/logic.py +46 -0
- {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
- {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
- {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
- {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
- {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
- {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
- {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
- {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
- {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
- {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
- {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
- {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
- {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
- {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
- {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
- {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
- {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
- {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
- {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
- {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
- {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
- {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
- {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
- {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
- {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
- {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
- nltk-3.3/nltk/collections.py +640 -0
- {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
- nltk-3.3/nltk/compat.py +365 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
- {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
- nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
- nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
- nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
- {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
- {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
- {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
- {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
- {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
- {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
- {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
- {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
- {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
- {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
- {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
- {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
- {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
- {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
- {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
- {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
- {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
- {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
- {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
- {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
- {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
- {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
- nltk-3.3/nltk/metrics/aline.py +607 -0
- {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
- {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
- {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
- {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
- {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
- {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
- {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
- {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
- {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
- {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
- {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
- {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
- {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
- {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
- nltk-3.3/nltk/parse/corenlp.py +780 -0
- {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
- {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
- {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
- {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
- {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
- {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
- {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
- {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
- {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
- {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
- {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
- {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
- {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
- {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
- {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
- {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
- {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
- {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
- {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
- {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
- {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
- {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
- {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
- {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
- {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
- {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
- nltk-3.3/nltk/stem/arlstem.py +369 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
- {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
- nltk-3.3/nltk/stem/porter.py +710 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
- {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
- {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
- {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
- {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
- {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
- {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
- {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
- {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
- {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
- {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
- {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
- {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
- {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
- {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
- {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
- {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
- {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
- {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
- {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
- {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
- {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
- nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
- {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
- {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
- {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
- nltk-3.3/nltk/test/collections.doctest +20 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
- {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
- {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
- nltk-3.3/nltk/test/concordance.doctest +55 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
- {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
- {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
- {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
- {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
- {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
- {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
- {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
- {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
- {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
- {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
- {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
- {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
- {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
- {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
- {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
- {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
- {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
- {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
- {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
- {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
- {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
- {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
- {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
- {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
- nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
- nltk-3.3/nltk/test/unit/test_aline.py +52 -0
- nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
- nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
- nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
- nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
- nltk-3.3/nltk/test/unit/test_senna.py +74 -0
- nltk-3.3/nltk/test/unit/test_stem.py +121 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
- nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
- nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
- nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
- nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
- nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
- {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
- nltk-3.3/nltk/tokenize/nist.py +173 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
- nltk-3.3/nltk/tokenize/repp.py +151 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
- nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
- nltk-3.3/nltk/tokenize/toktok.py +155 -0
- nltk-3.3/nltk/tokenize/treebank.py +363 -0
- nltk-3.3/nltk/tokenize/util.py +261 -0
- {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
- {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
- {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
- {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
- nltk-3.3/nltk/translate/chrf_score.py +137 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
- {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
- nltk-3.3/nltk/translate/gleu_score.py +193 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
- {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
- nltk-3.3/nltk/translate/nist_score.py +178 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
- {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
- {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
- {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
- {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
- {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
- {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
- {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
- {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
- nltk-3.3/nltk/util.py +751 -0
- {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
- {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
- nltk-3.3/nltk.egg-info/requires.txt +31 -0
- {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
- {nltk-3.2 → nltk-3.3}/setup.py +102 -75
- nltk-3.2/nltk/VERSION +0 -1
- nltk-3.2/nltk/compat.py +0 -680
- nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
- nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
- nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
- nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
- nltk-3.2/nltk/six.py +0 -868
- nltk-3.2/nltk/stem/porter.py +0 -692
- nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
- nltk-3.2/nltk/test/unit/test_stem.py +0 -36
- nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
- nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
- nltk-3.2/nltk/tokenize/treebank.py +0 -124
- nltk-3.2/nltk/tokenize/util.py +0 -90
- nltk-3.2/nltk/util.py +0 -1364
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
|
@@ -1,564 +1,564 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Natural Language Toolkit: ASCII visualization of NLTK trees
|
|
3
|
-
#
|
|
4
|
-
# Copyright (C) 2001-
|
|
5
|
-
# Author: Andreas van Cranenburgh <A.W.vanCranenburgh@uva.nl>
|
|
6
|
-
# Peter Ljunglöf <peter.ljunglof@gu.se>
|
|
7
|
-
# URL: <http://nltk.org/>
|
|
8
|
-
# For license information, see LICENSE.TXT
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
Pretty-printing of discontinuous trees.
|
|
12
|
-
Adapted from the disco-dop project, by Andreas van Cranenburgh.
|
|
13
|
-
https://github.com/andreasvc/disco-dop
|
|
14
|
-
|
|
15
|
-
Interesting reference (not used for this code):
|
|
16
|
-
T. Eschbach et al., Orth. Hypergraph Drawing, Journal of
|
|
17
|
-
Graph Algorithms and Applications, 10(2) 141--157 (2006)149.
|
|
18
|
-
http://jgaa.info/accepted/2006/EschbachGuentherBecker2006.10.2.pdf
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from __future__ import division, print_function, unicode_literals
|
|
22
|
-
|
|
23
|
-
from nltk.util import slice_bounds, OrderedDict
|
|
24
|
-
from nltk.compat import
|
|
25
|
-
from nltk.internals import raise_unorderable_types
|
|
26
|
-
from nltk.tree import Tree
|
|
27
|
-
|
|
28
|
-
import re
|
|
29
|
-
import sys
|
|
30
|
-
import codecs
|
|
31
|
-
from cgi import escape
|
|
32
|
-
from collections import defaultdict
|
|
33
|
-
from operator import itemgetter
|
|
34
|
-
from itertools import chain, islice
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ANSICOLOR = {
|
|
38
|
-
'black': 30,
|
|
39
|
-
'red': 31,
|
|
40
|
-
'green': 32,
|
|
41
|
-
'yellow': 33,
|
|
42
|
-
'blue': 34,
|
|
43
|
-
'magenta': 35,
|
|
44
|
-
'cyan': 36,
|
|
45
|
-
'white': 37,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@python_2_unicode_compatible
|
|
50
|
-
class TreePrettyPrinter(object):
|
|
51
|
-
"""
|
|
52
|
-
Pretty-print a tree in text format, either as ASCII or Unicode.
|
|
53
|
-
The tree can be a normal tree, or discontinuous.
|
|
54
|
-
|
|
55
|
-
``TreePrettyPrinter(tree, sentence=None, highlight=())``
|
|
56
|
-
creates an object from which different visualizations can be created.
|
|
57
|
-
|
|
58
|
-
:param tree: a Tree object.
|
|
59
|
-
:param sentence: a list of words (strings). If `sentence` is given,
|
|
60
|
-
`tree` must contain integers as leaves, which are taken as indices
|
|
61
|
-
in `sentence`. Using this you can display a discontinuous tree.
|
|
62
|
-
:param highlight: Optionally, a sequence of Tree objects in `tree` which
|
|
63
|
-
should be highlighted. Has the effect of only applying colors to nodes
|
|
64
|
-
in this sequence (nodes should be given as Tree objects, terminals as
|
|
65
|
-
indices).
|
|
66
|
-
|
|
67
|
-
>>> from nltk.tree import Tree
|
|
68
|
-
>>> tree = Tree.fromstring('(S (NP Mary) (VP walks))')
|
|
69
|
-
>>> print(TreePrettyPrinter(tree).text())
|
|
70
|
-
... # doctest: +NORMALIZE_WHITESPACE
|
|
71
|
-
S
|
|
72
|
-
____|____
|
|
73
|
-
NP VP
|
|
74
|
-
| |
|
|
75
|
-
Mary walks
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
def __init__(self, tree, sentence=None, highlight=()):
|
|
79
|
-
if sentence is None:
|
|
80
|
-
leaves = tree.leaves()
|
|
81
|
-
if (leaves and not any(len(a) == 0 for a in tree.subtrees())
|
|
82
|
-
and all(isinstance(a, int) for a in leaves)):
|
|
83
|
-
sentence = [str(a) for a in leaves]
|
|
84
|
-
else:
|
|
85
|
-
# this deals with empty nodes (frontier non-terminals)
|
|
86
|
-
# and multiple/mixed terminals under non-terminals.
|
|
87
|
-
tree = tree.copy(True)
|
|
88
|
-
sentence = []
|
|
89
|
-
for a in tree.subtrees():
|
|
90
|
-
if len(a) == 0:
|
|
91
|
-
a.append(len(sentence))
|
|
92
|
-
sentence.append(None)
|
|
93
|
-
elif any(not isinstance(b, Tree) for b in a):
|
|
94
|
-
for n, b in enumerate(a):
|
|
95
|
-
if not isinstance(b, Tree):
|
|
96
|
-
a[n] = len(sentence)
|
|
97
|
-
sentence.append('%s' % b)
|
|
98
|
-
self.nodes, self.coords, self.edges, self.highlight = self.nodecoords(
|
|
99
|
-
tree, sentence, highlight)
|
|
100
|
-
|
|
101
|
-
def __str__(self):
|
|
102
|
-
return self.text()
|
|
103
|
-
|
|
104
|
-
def __repr__(self):
|
|
105
|
-
return '<TreePrettyPrinter with %d nodes>' % len(self.nodes)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@staticmethod
|
|
109
|
-
def nodecoords(tree, sentence, highlight):
|
|
110
|
-
"""
|
|
111
|
-
Produce coordinates of nodes on a grid.
|
|
112
|
-
|
|
113
|
-
Objective:
|
|
114
|
-
|
|
115
|
-
- Produce coordinates for a non-overlapping placement of nodes and
|
|
116
|
-
horizontal lines.
|
|
117
|
-
- Order edges so that crossing edges cross a minimal number of previous
|
|
118
|
-
horizontal lines (never vertical lines).
|
|
119
|
-
|
|
120
|
-
Approach:
|
|
121
|
-
|
|
122
|
-
- bottom up level order traversal (start at terminals)
|
|
123
|
-
- at each level, identify nodes which cannot be on the same row
|
|
124
|
-
- identify nodes which cannot be in the same column
|
|
125
|
-
- place nodes into a grid at (row, column)
|
|
126
|
-
- order child-parent edges with crossing edges last
|
|
127
|
-
|
|
128
|
-
Coordinates are (row, column); the origin (0, 0) is at the top left;
|
|
129
|
-
the root node is on row 0. Coordinates do not consider the size of a
|
|
130
|
-
node (which depends on font, &c), so the width of a column of the grid
|
|
131
|
-
should be automatically determined by the element with the greatest
|
|
132
|
-
width in that column. Alternatively, the integer coordinates could be
|
|
133
|
-
converted to coordinates in which the distances between adjacent nodes
|
|
134
|
-
are non-uniform.
|
|
135
|
-
|
|
136
|
-
Produces tuple (nodes, coords, edges, highlighted) where:
|
|
137
|
-
|
|
138
|
-
- nodes[id]: Tree object for the node with this integer id
|
|
139
|
-
- coords[id]: (n, m) coordinate where to draw node with id in the grid
|
|
140
|
-
- edges[id]: parent id of node with this id (ordered dictionary)
|
|
141
|
-
- highlighted: set of ids that should be highlighted
|
|
142
|
-
"""
|
|
143
|
-
def findcell(m, matrix, startoflevel, children):
|
|
144
|
-
"""
|
|
145
|
-
Find vacant row, column index for node ``m``.
|
|
146
|
-
Iterate over current rows for this level (try lowest first)
|
|
147
|
-
and look for cell between first and last child of this node,
|
|
148
|
-
add new row to level if no free row available.
|
|
149
|
-
"""
|
|
150
|
-
candidates = [a for _, a in children[m]]
|
|
151
|
-
minidx, maxidx = min(candidates), max(candidates)
|
|
152
|
-
leaves = tree[m].leaves()
|
|
153
|
-
center = scale * sum(leaves) // len(leaves) # center of gravity
|
|
154
|
-
if minidx < maxidx and not minidx < center < maxidx:
|
|
155
|
-
center = sum(candidates) // len(candidates)
|
|
156
|
-
if max(candidates) - min(candidates) > 2 * scale:
|
|
157
|
-
center -= center % scale # round to unscaled coordinate
|
|
158
|
-
if minidx < maxidx and not minidx < center < maxidx:
|
|
159
|
-
center += scale
|
|
160
|
-
if ids[m] == 0:
|
|
161
|
-
startoflevel = len(matrix)
|
|
162
|
-
for rowidx in range(startoflevel, len(matrix) + 1):
|
|
163
|
-
if rowidx == len(matrix): # need to add a new row
|
|
164
|
-
matrix.append([vertline if a not in (corner, None)
|
|
165
|
-
else None for a in matrix[-1]])
|
|
166
|
-
row = matrix[rowidx]
|
|
167
|
-
i = j = center
|
|
168
|
-
if len(children[m]) == 1: # place unaries directly above child
|
|
169
|
-
return rowidx, next(iter(children[m]))[1]
|
|
170
|
-
elif all(a is None or a == vertline for a
|
|
171
|
-
in row[min(candidates):max(candidates) + 1]):
|
|
172
|
-
# find free column
|
|
173
|
-
for n in range(scale):
|
|
174
|
-
i = j = center + n
|
|
175
|
-
while j > minidx or i < maxidx:
|
|
176
|
-
if i < maxidx and (matrix[rowidx][i] is None
|
|
177
|
-
or i in candidates):
|
|
178
|
-
return rowidx, i
|
|
179
|
-
elif j > minidx and (matrix[rowidx][j] is None
|
|
180
|
-
or j in candidates):
|
|
181
|
-
return rowidx, j
|
|
182
|
-
i += scale
|
|
183
|
-
j -= scale
|
|
184
|
-
raise ValueError('could not find a free cell for:\n%s\n%s'
|
|
185
|
-
'min=%d; max=%d' % (tree[m], minidx, maxidx, dumpmatrix()))
|
|
186
|
-
|
|
187
|
-
def dumpmatrix():
|
|
188
|
-
"""Dump matrix contents for debugging purposes."""
|
|
189
|
-
return '\n'.join(
|
|
190
|
-
'%2d: %s' % (n, ' '.join(('%2r' % i)[:2] for i in row))
|
|
191
|
-
for n, row in enumerate(matrix))
|
|
192
|
-
|
|
193
|
-
leaves = tree.leaves()
|
|
194
|
-
if not all(isinstance(n, int) for n in leaves):
|
|
195
|
-
raise ValueError('All leaves must be integer indices.')
|
|
196
|
-
if len(leaves) != len(set(leaves)):
|
|
197
|
-
raise ValueError('Indices must occur at most once.')
|
|
198
|
-
if not all(0 <= n < len(sentence) for n in leaves):
|
|
199
|
-
raise ValueError('All leaves must be in the interval 0..n '
|
|
200
|
-
'with n=len(sentence)\ntokens: %d indices: '
|
|
201
|
-
'%r\nsentence: %s' % (len(sentence), tree.leaves(), sentence))
|
|
202
|
-
vertline, corner = -1, -2 # constants
|
|
203
|
-
tree = tree.copy(True)
|
|
204
|
-
for a in tree.subtrees():
|
|
205
|
-
a.sort(key=lambda n: min(n.leaves()) if isinstance(n, Tree) else n)
|
|
206
|
-
scale = 2
|
|
207
|
-
crossed = set()
|
|
208
|
-
# internal nodes and lexical nodes (no frontiers)
|
|
209
|
-
positions = tree.treepositions()
|
|
210
|
-
maxdepth = max(map(len, positions)) + 1
|
|
211
|
-
childcols = defaultdict(set)
|
|
212
|
-
matrix = [[None] * (len(sentence) * scale)]
|
|
213
|
-
nodes = {}
|
|
214
|
-
ids = dict((a, n) for n, a in enumerate(positions))
|
|
215
|
-
highlighted_nodes = set(n for a, n in ids.items()
|
|
216
|
-
if not highlight or tree[a] in highlight)
|
|
217
|
-
levels = dict((n, []) for n in range(maxdepth - 1))
|
|
218
|
-
terminals = []
|
|
219
|
-
for a in positions:
|
|
220
|
-
node = tree[a]
|
|
221
|
-
if isinstance(node, Tree):
|
|
222
|
-
levels[maxdepth - node.height()].append(a)
|
|
223
|
-
else:
|
|
224
|
-
terminals.append(a)
|
|
225
|
-
|
|
226
|
-
for n in levels:
|
|
227
|
-
levels[n].sort(key=lambda n: max(tree[n].leaves())
|
|
228
|
-
- min(tree[n].leaves()))
|
|
229
|
-
terminals.sort()
|
|
230
|
-
positions = set(positions)
|
|
231
|
-
|
|
232
|
-
for m in terminals:
|
|
233
|
-
i = int(tree[m]) * scale
|
|
234
|
-
assert matrix[0][i] is None, (matrix[0][i], m, i)
|
|
235
|
-
matrix[0][i] = ids[m]
|
|
236
|
-
nodes[ids[m]] = sentence[tree[m]]
|
|
237
|
-
if nodes[ids[m]] is None:
|
|
238
|
-
nodes[ids[m]] = '...'
|
|
239
|
-
highlighted_nodes.discard(ids[m])
|
|
240
|
-
positions.remove(m)
|
|
241
|
-
childcols[m[:-1]].add((0, i))
|
|
242
|
-
|
|
243
|
-
# add other nodes centered on their children,
|
|
244
|
-
# if the center is already taken, back off
|
|
245
|
-
# to the left and right alternately, until an empty cell is found.
|
|
246
|
-
for n in sorted(levels, reverse=True):
|
|
247
|
-
nodesatdepth = levels[n]
|
|
248
|
-
startoflevel = len(matrix)
|
|
249
|
-
matrix.append([vertline if a not in (corner, None) else None
|
|
250
|
-
for a in matrix[-1]])
|
|
251
|
-
for m in nodesatdepth: # [::-1]:
|
|
252
|
-
if n < maxdepth - 1 and childcols[m]:
|
|
253
|
-
_, pivot = min(childcols[m], key=itemgetter(1))
|
|
254
|
-
if (set(a[:-1] for row in matrix[:-1] for a in row[:pivot]
|
|
255
|
-
if isinstance(a, tuple)) &
|
|
256
|
-
set(a[:-1] for row in matrix[:-1] for a in row[pivot:]
|
|
257
|
-
if isinstance(a, tuple))):
|
|
258
|
-
crossed.add(m)
|
|
259
|
-
|
|
260
|
-
rowidx, i = findcell(m, matrix, startoflevel, childcols)
|
|
261
|
-
positions.remove(m)
|
|
262
|
-
|
|
263
|
-
# block positions where children of this node branch out
|
|
264
|
-
for _, x in childcols[m]:
|
|
265
|
-
matrix[rowidx][x] = corner
|
|
266
|
-
# assert m == () or matrix[rowidx][i] in (None, corner), (
|
|
267
|
-
# matrix[rowidx][i], m, str(tree), ' '.join(sentence))
|
|
268
|
-
# node itself
|
|
269
|
-
matrix[rowidx][i] = ids[m]
|
|
270
|
-
nodes[ids[m]] = tree[m]
|
|
271
|
-
# add column to the set of children for its parent
|
|
272
|
-
if m != ():
|
|
273
|
-
childcols[m[:-1]].add((rowidx, i))
|
|
274
|
-
assert len(positions) == 0
|
|
275
|
-
|
|
276
|
-
# remove unused columns, right to left
|
|
277
|
-
for m in range(scale * len(sentence) - 1, -1, -1):
|
|
278
|
-
if not any(isinstance(row[m], (Tree, int))
|
|
279
|
-
for row in matrix):
|
|
280
|
-
for row in matrix:
|
|
281
|
-
del row[m]
|
|
282
|
-
|
|
283
|
-
# remove unused rows, reverse
|
|
284
|
-
matrix = [row for row in reversed(matrix)
|
|
285
|
-
if not all(a is None or a == vertline for a in row)]
|
|
286
|
-
|
|
287
|
-
# collect coordinates of nodes
|
|
288
|
-
coords = {}
|
|
289
|
-
for n, _ in enumerate(matrix):
|
|
290
|
-
for m, i in enumerate(matrix[n]):
|
|
291
|
-
if isinstance(i, int) and i >= 0:
|
|
292
|
-
coords[i] = n, m
|
|
293
|
-
|
|
294
|
-
# move crossed edges last
|
|
295
|
-
positions = sorted([a for level in levels.values()
|
|
296
|
-
for a in level], key=lambda a: a[:-1] in crossed)
|
|
297
|
-
|
|
298
|
-
# collect edges from node to node
|
|
299
|
-
edges = OrderedDict()
|
|
300
|
-
for i in reversed(positions):
|
|
301
|
-
for j, _ in enumerate(tree[i]):
|
|
302
|
-
edges[ids[i + (j, )]] = ids[i]
|
|
303
|
-
|
|
304
|
-
return nodes, coords, edges, highlighted_nodes
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def text(self, nodedist=1, unicodelines=False, html=False, ansi=False,
|
|
308
|
-
nodecolor='blue', leafcolor='red', funccolor='green',
|
|
309
|
-
abbreviate=None, maxwidth=16):
|
|
310
|
-
"""
|
|
311
|
-
:return: ASCII art for a discontinuous tree.
|
|
312
|
-
|
|
313
|
-
:param unicodelines: whether to use Unicode line drawing characters
|
|
314
|
-
instead of plain (7-bit) ASCII.
|
|
315
|
-
:param html: whether to wrap output in html code (default plain text).
|
|
316
|
-
:param ansi: whether to produce colors with ANSI escape sequences
|
|
317
|
-
(only effective when html==False).
|
|
318
|
-
:param leafcolor, nodecolor: specify colors of leaves and phrasal
|
|
319
|
-
nodes; effective when either html or ansi is True.
|
|
320
|
-
:param abbreviate: if True, abbreviate labels longer than 5 characters.
|
|
321
|
-
If integer, abbreviate labels longer than `abbr` characters.
|
|
322
|
-
:param maxwidth: maximum number of characters before a label starts to
|
|
323
|
-
wrap; pass None to disable.
|
|
324
|
-
"""
|
|
325
|
-
if abbreviate == True:
|
|
326
|
-
abbreviate = 5
|
|
327
|
-
if unicodelines:
|
|
328
|
-
horzline = '\u2500'
|
|
329
|
-
leftcorner = '\u250c'
|
|
330
|
-
rightcorner = '\u2510'
|
|
331
|
-
vertline = ' \u2502 '
|
|
332
|
-
tee = horzline + '\u252C' + horzline
|
|
333
|
-
bottom = horzline + '\u2534' + horzline
|
|
334
|
-
cross = horzline + '\u253c' + horzline
|
|
335
|
-
ellipsis = '\u2026'
|
|
336
|
-
else:
|
|
337
|
-
horzline = '_'
|
|
338
|
-
leftcorner = rightcorner = ' '
|
|
339
|
-
vertline = ' | '
|
|
340
|
-
tee = 3 * horzline
|
|
341
|
-
cross = bottom = '_|_'
|
|
342
|
-
ellipsis = '.'
|
|
343
|
-
|
|
344
|
-
def crosscell(cur, x=vertline):
|
|
345
|
-
"""Overwrite center of this cell with a vertical branch."""
|
|
346
|
-
splitl = len(cur) - len(cur) // 2 - len(x) // 2 - 1
|
|
347
|
-
lst = list(cur)
|
|
348
|
-
lst[splitl:splitl + len(x)] = list(x)
|
|
349
|
-
return ''.join(lst)
|
|
350
|
-
|
|
351
|
-
result = []
|
|
352
|
-
matrix = defaultdict(dict)
|
|
353
|
-
maxnodewith = defaultdict(lambda: 3)
|
|
354
|
-
maxnodeheight = defaultdict(lambda: 1)
|
|
355
|
-
maxcol = 0
|
|
356
|
-
minchildcol = {}
|
|
357
|
-
maxchildcol = {}
|
|
358
|
-
childcols = defaultdict(set)
|
|
359
|
-
labels = {}
|
|
360
|
-
wrapre = re.compile('(.{%d,%d}\\b\\W*|.{%d})' % (
|
|
361
|
-
maxwidth - 4, maxwidth, maxwidth))
|
|
362
|
-
# collect labels and coordinates
|
|
363
|
-
for a in self.nodes:
|
|
364
|
-
row, column = self.coords[a]
|
|
365
|
-
matrix[row][column] = a
|
|
366
|
-
maxcol = max(maxcol, column)
|
|
367
|
-
label = (self.nodes[a].label() if isinstance(self.nodes[a], Tree)
|
|
368
|
-
else self.nodes[a])
|
|
369
|
-
if abbreviate and len(label) > abbreviate:
|
|
370
|
-
label = label[:abbreviate] + ellipsis
|
|
371
|
-
if maxwidth and len(label) > maxwidth:
|
|
372
|
-
label = wrapre.sub(r'\1\n', label).strip()
|
|
373
|
-
label = label.split('\n')
|
|
374
|
-
maxnodeheight[row] = max(maxnodeheight[row], len(label))
|
|
375
|
-
maxnodewith[column] = max(maxnodewith[column], max(map(len, label)))
|
|
376
|
-
labels[a] = label
|
|
377
|
-
if a not in self.edges:
|
|
378
|
-
continue # e.g., root
|
|
379
|
-
parent = self.edges[a]
|
|
380
|
-
childcols[parent].add((row, column))
|
|
381
|
-
minchildcol[parent] = min(minchildcol.get(parent, column), column)
|
|
382
|
-
maxchildcol[parent] = max(maxchildcol.get(parent, column), column)
|
|
383
|
-
# bottom up level order traversal
|
|
384
|
-
for row in sorted(matrix, reverse=True):
|
|
385
|
-
noderows = [[''.center(maxnodewith[col]) for col in range(maxcol + 1)]
|
|
386
|
-
for _ in range(maxnodeheight[row])]
|
|
387
|
-
branchrow = [''.center(maxnodewith[col]) for col in range(maxcol + 1)]
|
|
388
|
-
for col in matrix[row]:
|
|
389
|
-
n = matrix[row][col]
|
|
390
|
-
node = self.nodes[n]
|
|
391
|
-
text = labels[n]
|
|
392
|
-
if isinstance(node, Tree):
|
|
393
|
-
# draw horizontal branch towards children for this node
|
|
394
|
-
if n in minchildcol and minchildcol[n] < maxchildcol[n]:
|
|
395
|
-
i, j = minchildcol[n], maxchildcol[n]
|
|
396
|
-
a, b = (maxnodewith[i] + 1) // 2 - 1, maxnodewith[j] // 2
|
|
397
|
-
branchrow[i] = ((' ' * a) + leftcorner).ljust(
|
|
398
|
-
maxnodewith[i], horzline)
|
|
399
|
-
branchrow[j] = (rightcorner + (' ' * b)).rjust(
|
|
400
|
-
maxnodewith[j], horzline)
|
|
401
|
-
for i in range(minchildcol[n] + 1, maxchildcol[n]):
|
|
402
|
-
if i == col and any(
|
|
403
|
-
a == i for _, a in childcols[n]):
|
|
404
|
-
line = cross
|
|
405
|
-
elif i == col:
|
|
406
|
-
line = bottom
|
|
407
|
-
elif any(a == i for _, a in childcols[n]):
|
|
408
|
-
line = tee
|
|
409
|
-
else:
|
|
410
|
-
line = horzline
|
|
411
|
-
branchrow[i] = line.center(maxnodewith[i], horzline)
|
|
412
|
-
else: # if n and n in minchildcol:
|
|
413
|
-
branchrow[col] = crosscell(branchrow[col])
|
|
414
|
-
text = [a.center(maxnodewith[col]) for a in text]
|
|
415
|
-
color = nodecolor if isinstance(node, Tree) else leafcolor
|
|
416
|
-
if isinstance(node, Tree) and node.label().startswith('-'):
|
|
417
|
-
color = funccolor
|
|
418
|
-
if html:
|
|
419
|
-
text = [escape(a) for a in text]
|
|
420
|
-
if n in self.highlight:
|
|
421
|
-
text = ['<font color=%s>%s</font>' % (
|
|
422
|
-
color, a) for a in text]
|
|
423
|
-
elif ansi and n in self.highlight:
|
|
424
|
-
text = ['\x1b[%d;1m%s\x1b[0m' % (
|
|
425
|
-
ANSICOLOR[color], a) for a in text]
|
|
426
|
-
for x in range(maxnodeheight[row]):
|
|
427
|
-
# draw vertical lines in partially filled multiline node
|
|
428
|
-
# labels, but only if it's not a frontier node.
|
|
429
|
-
noderows[x][col] = (text[x] if x < len(text)
|
|
430
|
-
else (vertline if childcols[n] else ' ').center(
|
|
431
|
-
maxnodewith[col], ' '))
|
|
432
|
-
# for each column, if there is a node below us which has a parent
|
|
433
|
-
# above us, draw a vertical branch in that column.
|
|
434
|
-
if row != max(matrix):
|
|
435
|
-
for n, (childrow, col) in self.coords.items():
|
|
436
|
-
if (n > 0 and
|
|
437
|
-
self.coords[self.edges[n]][0] < row < childrow):
|
|
438
|
-
branchrow[col] = crosscell(branchrow[col])
|
|
439
|
-
if col not in matrix[row]:
|
|
440
|
-
for noderow in noderows:
|
|
441
|
-
noderow[col] = crosscell(noderow[col])
|
|
442
|
-
branchrow = [a + ((a[-1] if a[-1] != ' ' else b[0]) * nodedist)
|
|
443
|
-
for a, b in zip(branchrow, branchrow[1:] + [' '])]
|
|
444
|
-
result.append(''.join(branchrow))
|
|
445
|
-
result.extend((' ' * nodedist).join(noderow)
|
|
446
|
-
for noderow in reversed(noderows))
|
|
447
|
-
return '\n'.join(reversed(result)) + '\n'
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
def svg(self, nodecolor='blue', leafcolor='red', funccolor='green'):
|
|
451
|
-
"""
|
|
452
|
-
:return: SVG representation of a tree.
|
|
453
|
-
"""
|
|
454
|
-
fontsize = 12
|
|
455
|
-
hscale = 40
|
|
456
|
-
vscale = 25
|
|
457
|
-
hstart = vstart = 20
|
|
458
|
-
width = max(col for _, col in self.coords.values())
|
|
459
|
-
height = max(row for row, _ in self.coords.values())
|
|
460
|
-
result = ['<svg version="1.1" xmlns="http://www.w3.org/2000/svg" '
|
|
461
|
-
'width="%dem" height="%dem" viewBox="%d %d %d %d">' % (
|
|
462
|
-
width * 3,
|
|
463
|
-
height * 2.5,
|
|
464
|
-
-hstart, -vstart,
|
|
465
|
-
width * hscale + 3 * hstart,
|
|
466
|
-
height * vscale + 3 * vstart)
|
|
467
|
-
]
|
|
468
|
-
|
|
469
|
-
children = defaultdict(set)
|
|
470
|
-
for n in self.nodes:
|
|
471
|
-
if n:
|
|
472
|
-
children[self.edges[n]].add(n)
|
|
473
|
-
|
|
474
|
-
# horizontal branches from nodes to children
|
|
475
|
-
for node in self.nodes:
|
|
476
|
-
if not children[node]:
|
|
477
|
-
continue
|
|
478
|
-
y, x = self.coords[node]
|
|
479
|
-
x *= hscale
|
|
480
|
-
y *= vscale
|
|
481
|
-
x += hstart
|
|
482
|
-
y += vstart + fontsize // 2
|
|
483
|
-
childx = [self.coords[c][1] for c in children[node]]
|
|
484
|
-
xmin = hstart + hscale * min(childx)
|
|
485
|
-
xmax = hstart + hscale * max(childx)
|
|
486
|
-
result.append(
|
|
487
|
-
'\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
|
|
488
|
-
'points="%g,%g %g,%g" />' % (xmin, y, xmax, y))
|
|
489
|
-
result.append(
|
|
490
|
-
'\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
|
|
491
|
-
'points="%g,%g %g,%g" />' % (x, y, x, y - fontsize // 3))
|
|
492
|
-
|
|
493
|
-
# vertical branches from children to parents
|
|
494
|
-
for child, parent in self.edges.items():
|
|
495
|
-
y, _ = self.coords[parent]
|
|
496
|
-
y *= vscale
|
|
497
|
-
y += vstart + fontsize // 2
|
|
498
|
-
childy, childx = self.coords[child]
|
|
499
|
-
childx *= hscale
|
|
500
|
-
childy *= vscale
|
|
501
|
-
childx += hstart
|
|
502
|
-
childy += vstart - fontsize
|
|
503
|
-
result += [
|
|
504
|
-
'\t<polyline style="stroke:white; stroke-width:10; fill:none;"'
|
|
505
|
-
' points="%g,%g %g,%g" />' % (childx, childy, childx, y + 5),
|
|
506
|
-
'\t<polyline style="stroke:black; stroke-width:1; fill:none;"'
|
|
507
|
-
' points="%g,%g %g,%g" />' % (childx, childy, childx, y),
|
|
508
|
-
]
|
|
509
|
-
|
|
510
|
-
# write nodes with coordinates
|
|
511
|
-
for n, (row, column) in self.coords.items():
|
|
512
|
-
node = self.nodes[n]
|
|
513
|
-
x = column * hscale + hstart
|
|
514
|
-
y = row * vscale + vstart
|
|
515
|
-
if n in self.highlight:
|
|
516
|
-
color = nodecolor if isinstance(node, Tree) else leafcolor
|
|
517
|
-
if isinstance(node, Tree) and node.label().startswith('-'):
|
|
518
|
-
color = funccolor
|
|
519
|
-
else:
|
|
520
|
-
color = 'black'
|
|
521
|
-
result += ['\t<text style="text-anchor: middle; fill: %s; '
|
|
522
|
-
'font-size: %dpx;" x="%g" y="%g">%s</text>' % (
|
|
523
|
-
color, fontsize, x, y,
|
|
524
|
-
escape(node.label() if isinstance(node, Tree)
|
|
525
|
-
else node))]
|
|
526
|
-
|
|
527
|
-
result += ['</svg>']
|
|
528
|
-
return '\n'.join(result)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def test():
|
|
532
|
-
"""Do some tree drawing tests."""
|
|
533
|
-
def print_tree(n, tree, sentence=None, ansi=True, **xargs):
|
|
534
|
-
print()
|
|
535
|
-
print('{0}: "{1}"'.format(n, ' '.join(sentence or tree.leaves())))
|
|
536
|
-
print(tree)
|
|
537
|
-
print()
|
|
538
|
-
drawtree = TreePrettyPrinter(tree, sentence)
|
|
539
|
-
try:
|
|
540
|
-
print(drawtree.text(unicodelines=ansi, ansi=ansi, **xargs))
|
|
541
|
-
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
542
|
-
print(drawtree.text(unicodelines=False, ansi=False, **xargs))
|
|
543
|
-
|
|
544
|
-
from nltk.corpus import treebank
|
|
545
|
-
for n in [0, 1440, 1591, 2771, 2170]:
|
|
546
|
-
tree = treebank.parsed_sents()[n]
|
|
547
|
-
print_tree(n, tree, nodedist=2, maxwidth=8)
|
|
548
|
-
print()
|
|
549
|
-
print('ASCII version:')
|
|
550
|
-
print(TreePrettyPrinter(tree).text(nodedist=2))
|
|
551
|
-
|
|
552
|
-
tree = Tree.fromstring(
|
|
553
|
-
'(top (punct 8) (smain (noun 0) (verb 1) (inf (verb 5) (inf (verb 6) '
|
|
554
|
-
'(conj (inf (pp (prep 2) (np (det 3) (noun 4))) (verb 7)) (inf (verb 9)) '
|
|
555
|
-
'(vg 10) (inf (verb 11)))))) (punct 12))', read_leaf=int)
|
|
556
|
-
sentence = ('Ze had met haar moeder kunnen gaan winkelen ,'
|
|
557
|
-
' zwemmen of terrassen .'.split())
|
|
558
|
-
print_tree('Discontinuous tree', tree, sentence, nodedist=2)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
__all__ = ['TreePrettyPrinter']
|
|
562
|
-
|
|
563
|
-
if __name__ == '__main__':
|
|
564
|
-
test()
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Natural Language Toolkit: ASCII visualization of NLTK trees
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2001-2018 NLTK Project
|
|
5
|
+
# Author: Andreas van Cranenburgh <A.W.vanCranenburgh@uva.nl>
|
|
6
|
+
# Peter Ljunglöf <peter.ljunglof@gu.se>
|
|
7
|
+
# URL: <http://nltk.org/>
|
|
8
|
+
# For license information, see LICENSE.TXT
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Pretty-printing of discontinuous trees.
|
|
12
|
+
Adapted from the disco-dop project, by Andreas van Cranenburgh.
|
|
13
|
+
https://github.com/andreasvc/disco-dop
|
|
14
|
+
|
|
15
|
+
Interesting reference (not used for this code):
|
|
16
|
+
T. Eschbach et al., Orth. Hypergraph Drawing, Journal of
|
|
17
|
+
Graph Algorithms and Applications, 10(2) 141--157 (2006)149.
|
|
18
|
+
http://jgaa.info/accepted/2006/EschbachGuentherBecker2006.10.2.pdf
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import division, print_function, unicode_literals
|
|
22
|
+
|
|
23
|
+
from nltk.util import slice_bounds, OrderedDict
|
|
24
|
+
from nltk.compat import python_2_unicode_compatible, unicode_repr
|
|
25
|
+
from nltk.internals import raise_unorderable_types
|
|
26
|
+
from nltk.tree import Tree
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
import codecs
|
|
31
|
+
from cgi import escape
|
|
32
|
+
from collections import defaultdict
|
|
33
|
+
from operator import itemgetter
|
|
34
|
+
from itertools import chain, islice
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ANSICOLOR = {
|
|
38
|
+
'black': 30,
|
|
39
|
+
'red': 31,
|
|
40
|
+
'green': 32,
|
|
41
|
+
'yellow': 33,
|
|
42
|
+
'blue': 34,
|
|
43
|
+
'magenta': 35,
|
|
44
|
+
'cyan': 36,
|
|
45
|
+
'white': 37,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@python_2_unicode_compatible
|
|
50
|
+
class TreePrettyPrinter(object):
|
|
51
|
+
"""
|
|
52
|
+
Pretty-print a tree in text format, either as ASCII or Unicode.
|
|
53
|
+
The tree can be a normal tree, or discontinuous.
|
|
54
|
+
|
|
55
|
+
``TreePrettyPrinter(tree, sentence=None, highlight=())``
|
|
56
|
+
creates an object from which different visualizations can be created.
|
|
57
|
+
|
|
58
|
+
:param tree: a Tree object.
|
|
59
|
+
:param sentence: a list of words (strings). If `sentence` is given,
|
|
60
|
+
`tree` must contain integers as leaves, which are taken as indices
|
|
61
|
+
in `sentence`. Using this you can display a discontinuous tree.
|
|
62
|
+
:param highlight: Optionally, a sequence of Tree objects in `tree` which
|
|
63
|
+
should be highlighted. Has the effect of only applying colors to nodes
|
|
64
|
+
in this sequence (nodes should be given as Tree objects, terminals as
|
|
65
|
+
indices).
|
|
66
|
+
|
|
67
|
+
>>> from nltk.tree import Tree
|
|
68
|
+
>>> tree = Tree.fromstring('(S (NP Mary) (VP walks))')
|
|
69
|
+
>>> print(TreePrettyPrinter(tree).text())
|
|
70
|
+
... # doctest: +NORMALIZE_WHITESPACE
|
|
71
|
+
S
|
|
72
|
+
____|____
|
|
73
|
+
NP VP
|
|
74
|
+
| |
|
|
75
|
+
Mary walks
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, tree, sentence=None, highlight=()):
|
|
79
|
+
if sentence is None:
|
|
80
|
+
leaves = tree.leaves()
|
|
81
|
+
if (leaves and not any(len(a) == 0 for a in tree.subtrees())
|
|
82
|
+
and all(isinstance(a, int) for a in leaves)):
|
|
83
|
+
sentence = [str(a) for a in leaves]
|
|
84
|
+
else:
|
|
85
|
+
# this deals with empty nodes (frontier non-terminals)
|
|
86
|
+
# and multiple/mixed terminals under non-terminals.
|
|
87
|
+
tree = tree.copy(True)
|
|
88
|
+
sentence = []
|
|
89
|
+
for a in tree.subtrees():
|
|
90
|
+
if len(a) == 0:
|
|
91
|
+
a.append(len(sentence))
|
|
92
|
+
sentence.append(None)
|
|
93
|
+
elif any(not isinstance(b, Tree) for b in a):
|
|
94
|
+
for n, b in enumerate(a):
|
|
95
|
+
if not isinstance(b, Tree):
|
|
96
|
+
a[n] = len(sentence)
|
|
97
|
+
sentence.append('%s' % b)
|
|
98
|
+
self.nodes, self.coords, self.edges, self.highlight = self.nodecoords(
|
|
99
|
+
tree, sentence, highlight)
|
|
100
|
+
|
|
101
|
+
def __str__(self):
|
|
102
|
+
return self.text()
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return '<TreePrettyPrinter with %d nodes>' % len(self.nodes)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def nodecoords(tree, sentence, highlight):
|
|
110
|
+
"""
|
|
111
|
+
Produce coordinates of nodes on a grid.
|
|
112
|
+
|
|
113
|
+
Objective:
|
|
114
|
+
|
|
115
|
+
- Produce coordinates for a non-overlapping placement of nodes and
|
|
116
|
+
horizontal lines.
|
|
117
|
+
- Order edges so that crossing edges cross a minimal number of previous
|
|
118
|
+
horizontal lines (never vertical lines).
|
|
119
|
+
|
|
120
|
+
Approach:
|
|
121
|
+
|
|
122
|
+
- bottom up level order traversal (start at terminals)
|
|
123
|
+
- at each level, identify nodes which cannot be on the same row
|
|
124
|
+
- identify nodes which cannot be in the same column
|
|
125
|
+
- place nodes into a grid at (row, column)
|
|
126
|
+
- order child-parent edges with crossing edges last
|
|
127
|
+
|
|
128
|
+
Coordinates are (row, column); the origin (0, 0) is at the top left;
|
|
129
|
+
the root node is on row 0. Coordinates do not consider the size of a
|
|
130
|
+
node (which depends on font, &c), so the width of a column of the grid
|
|
131
|
+
should be automatically determined by the element with the greatest
|
|
132
|
+
width in that column. Alternatively, the integer coordinates could be
|
|
133
|
+
converted to coordinates in which the distances between adjacent nodes
|
|
134
|
+
are non-uniform.
|
|
135
|
+
|
|
136
|
+
Produces tuple (nodes, coords, edges, highlighted) where:
|
|
137
|
+
|
|
138
|
+
- nodes[id]: Tree object for the node with this integer id
|
|
139
|
+
- coords[id]: (n, m) coordinate where to draw node with id in the grid
|
|
140
|
+
- edges[id]: parent id of node with this id (ordered dictionary)
|
|
141
|
+
- highlighted: set of ids that should be highlighted
|
|
142
|
+
"""
|
|
143
|
+
def findcell(m, matrix, startoflevel, children):
|
|
144
|
+
"""
|
|
145
|
+
Find vacant row, column index for node ``m``.
|
|
146
|
+
Iterate over current rows for this level (try lowest first)
|
|
147
|
+
and look for cell between first and last child of this node,
|
|
148
|
+
add new row to level if no free row available.
|
|
149
|
+
"""
|
|
150
|
+
candidates = [a for _, a in children[m]]
|
|
151
|
+
minidx, maxidx = min(candidates), max(candidates)
|
|
152
|
+
leaves = tree[m].leaves()
|
|
153
|
+
center = scale * sum(leaves) // len(leaves) # center of gravity
|
|
154
|
+
if minidx < maxidx and not minidx < center < maxidx:
|
|
155
|
+
center = sum(candidates) // len(candidates)
|
|
156
|
+
if max(candidates) - min(candidates) > 2 * scale:
|
|
157
|
+
center -= center % scale # round to unscaled coordinate
|
|
158
|
+
if minidx < maxidx and not minidx < center < maxidx:
|
|
159
|
+
center += scale
|
|
160
|
+
if ids[m] == 0:
|
|
161
|
+
startoflevel = len(matrix)
|
|
162
|
+
for rowidx in range(startoflevel, len(matrix) + 1):
|
|
163
|
+
if rowidx == len(matrix): # need to add a new row
|
|
164
|
+
matrix.append([vertline if a not in (corner, None)
|
|
165
|
+
else None for a in matrix[-1]])
|
|
166
|
+
row = matrix[rowidx]
|
|
167
|
+
i = j = center
|
|
168
|
+
if len(children[m]) == 1: # place unaries directly above child
|
|
169
|
+
return rowidx, next(iter(children[m]))[1]
|
|
170
|
+
elif all(a is None or a == vertline for a
|
|
171
|
+
in row[min(candidates):max(candidates) + 1]):
|
|
172
|
+
# find free column
|
|
173
|
+
for n in range(scale):
|
|
174
|
+
i = j = center + n
|
|
175
|
+
while j > minidx or i < maxidx:
|
|
176
|
+
if i < maxidx and (matrix[rowidx][i] is None
|
|
177
|
+
or i in candidates):
|
|
178
|
+
return rowidx, i
|
|
179
|
+
elif j > minidx and (matrix[rowidx][j] is None
|
|
180
|
+
or j in candidates):
|
|
181
|
+
return rowidx, j
|
|
182
|
+
i += scale
|
|
183
|
+
j -= scale
|
|
184
|
+
raise ValueError('could not find a free cell for:\n%s\n%s'
|
|
185
|
+
'min=%d; max=%d' % (tree[m], minidx, maxidx, dumpmatrix()))
|
|
186
|
+
|
|
187
|
+
def dumpmatrix():
|
|
188
|
+
"""Dump matrix contents for debugging purposes."""
|
|
189
|
+
return '\n'.join(
|
|
190
|
+
'%2d: %s' % (n, ' '.join(('%2r' % i)[:2] for i in row))
|
|
191
|
+
for n, row in enumerate(matrix))
|
|
192
|
+
|
|
193
|
+
leaves = tree.leaves()
|
|
194
|
+
if not all(isinstance(n, int) for n in leaves):
|
|
195
|
+
raise ValueError('All leaves must be integer indices.')
|
|
196
|
+
if len(leaves) != len(set(leaves)):
|
|
197
|
+
raise ValueError('Indices must occur at most once.')
|
|
198
|
+
if not all(0 <= n < len(sentence) for n in leaves):
|
|
199
|
+
raise ValueError('All leaves must be in the interval 0..n '
|
|
200
|
+
'with n=len(sentence)\ntokens: %d indices: '
|
|
201
|
+
'%r\nsentence: %s' % (len(sentence), tree.leaves(), sentence))
|
|
202
|
+
vertline, corner = -1, -2 # constants
|
|
203
|
+
tree = tree.copy(True)
|
|
204
|
+
for a in tree.subtrees():
|
|
205
|
+
a.sort(key=lambda n: min(n.leaves()) if isinstance(n, Tree) else n)
|
|
206
|
+
scale = 2
|
|
207
|
+
crossed = set()
|
|
208
|
+
# internal nodes and lexical nodes (no frontiers)
|
|
209
|
+
positions = tree.treepositions()
|
|
210
|
+
maxdepth = max(map(len, positions)) + 1
|
|
211
|
+
childcols = defaultdict(set)
|
|
212
|
+
matrix = [[None] * (len(sentence) * scale)]
|
|
213
|
+
nodes = {}
|
|
214
|
+
ids = dict((a, n) for n, a in enumerate(positions))
|
|
215
|
+
highlighted_nodes = set(n for a, n in ids.items()
|
|
216
|
+
if not highlight or tree[a] in highlight)
|
|
217
|
+
levels = dict((n, []) for n in range(maxdepth - 1))
|
|
218
|
+
terminals = []
|
|
219
|
+
for a in positions:
|
|
220
|
+
node = tree[a]
|
|
221
|
+
if isinstance(node, Tree):
|
|
222
|
+
levels[maxdepth - node.height()].append(a)
|
|
223
|
+
else:
|
|
224
|
+
terminals.append(a)
|
|
225
|
+
|
|
226
|
+
for n in levels:
|
|
227
|
+
levels[n].sort(key=lambda n: max(tree[n].leaves())
|
|
228
|
+
- min(tree[n].leaves()))
|
|
229
|
+
terminals.sort()
|
|
230
|
+
positions = set(positions)
|
|
231
|
+
|
|
232
|
+
for m in terminals:
|
|
233
|
+
i = int(tree[m]) * scale
|
|
234
|
+
assert matrix[0][i] is None, (matrix[0][i], m, i)
|
|
235
|
+
matrix[0][i] = ids[m]
|
|
236
|
+
nodes[ids[m]] = sentence[tree[m]]
|
|
237
|
+
if nodes[ids[m]] is None:
|
|
238
|
+
nodes[ids[m]] = '...'
|
|
239
|
+
highlighted_nodes.discard(ids[m])
|
|
240
|
+
positions.remove(m)
|
|
241
|
+
childcols[m[:-1]].add((0, i))
|
|
242
|
+
|
|
243
|
+
# add other nodes centered on their children,
|
|
244
|
+
# if the center is already taken, back off
|
|
245
|
+
# to the left and right alternately, until an empty cell is found.
|
|
246
|
+
for n in sorted(levels, reverse=True):
|
|
247
|
+
nodesatdepth = levels[n]
|
|
248
|
+
startoflevel = len(matrix)
|
|
249
|
+
matrix.append([vertline if a not in (corner, None) else None
|
|
250
|
+
for a in matrix[-1]])
|
|
251
|
+
for m in nodesatdepth: # [::-1]:
|
|
252
|
+
if n < maxdepth - 1 and childcols[m]:
|
|
253
|
+
_, pivot = min(childcols[m], key=itemgetter(1))
|
|
254
|
+
if (set(a[:-1] for row in matrix[:-1] for a in row[:pivot]
|
|
255
|
+
if isinstance(a, tuple)) &
|
|
256
|
+
set(a[:-1] for row in matrix[:-1] for a in row[pivot:]
|
|
257
|
+
if isinstance(a, tuple))):
|
|
258
|
+
crossed.add(m)
|
|
259
|
+
|
|
260
|
+
rowidx, i = findcell(m, matrix, startoflevel, childcols)
|
|
261
|
+
positions.remove(m)
|
|
262
|
+
|
|
263
|
+
# block positions where children of this node branch out
|
|
264
|
+
for _, x in childcols[m]:
|
|
265
|
+
matrix[rowidx][x] = corner
|
|
266
|
+
# assert m == () or matrix[rowidx][i] in (None, corner), (
|
|
267
|
+
# matrix[rowidx][i], m, str(tree), ' '.join(sentence))
|
|
268
|
+
# node itself
|
|
269
|
+
matrix[rowidx][i] = ids[m]
|
|
270
|
+
nodes[ids[m]] = tree[m]
|
|
271
|
+
# add column to the set of children for its parent
|
|
272
|
+
if m != ():
|
|
273
|
+
childcols[m[:-1]].add((rowidx, i))
|
|
274
|
+
assert len(positions) == 0
|
|
275
|
+
|
|
276
|
+
# remove unused columns, right to left
|
|
277
|
+
for m in range(scale * len(sentence) - 1, -1, -1):
|
|
278
|
+
if not any(isinstance(row[m], (Tree, int))
|
|
279
|
+
for row in matrix):
|
|
280
|
+
for row in matrix:
|
|
281
|
+
del row[m]
|
|
282
|
+
|
|
283
|
+
# remove unused rows, reverse
|
|
284
|
+
matrix = [row for row in reversed(matrix)
|
|
285
|
+
if not all(a is None or a == vertline for a in row)]
|
|
286
|
+
|
|
287
|
+
# collect coordinates of nodes
|
|
288
|
+
coords = {}
|
|
289
|
+
for n, _ in enumerate(matrix):
|
|
290
|
+
for m, i in enumerate(matrix[n]):
|
|
291
|
+
if isinstance(i, int) and i >= 0:
|
|
292
|
+
coords[i] = n, m
|
|
293
|
+
|
|
294
|
+
# move crossed edges last
|
|
295
|
+
positions = sorted([a for level in levels.values()
|
|
296
|
+
for a in level], key=lambda a: a[:-1] in crossed)
|
|
297
|
+
|
|
298
|
+
# collect edges from node to node
|
|
299
|
+
edges = OrderedDict()
|
|
300
|
+
for i in reversed(positions):
|
|
301
|
+
for j, _ in enumerate(tree[i]):
|
|
302
|
+
edges[ids[i + (j, )]] = ids[i]
|
|
303
|
+
|
|
304
|
+
return nodes, coords, edges, highlighted_nodes
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def text(self, nodedist=1, unicodelines=False, html=False, ansi=False,
|
|
308
|
+
nodecolor='blue', leafcolor='red', funccolor='green',
|
|
309
|
+
abbreviate=None, maxwidth=16):
|
|
310
|
+
"""
|
|
311
|
+
:return: ASCII art for a discontinuous tree.
|
|
312
|
+
|
|
313
|
+
:param unicodelines: whether to use Unicode line drawing characters
|
|
314
|
+
instead of plain (7-bit) ASCII.
|
|
315
|
+
:param html: whether to wrap output in html code (default plain text).
|
|
316
|
+
:param ansi: whether to produce colors with ANSI escape sequences
|
|
317
|
+
(only effective when html==False).
|
|
318
|
+
:param leafcolor, nodecolor: specify colors of leaves and phrasal
|
|
319
|
+
nodes; effective when either html or ansi is True.
|
|
320
|
+
:param abbreviate: if True, abbreviate labels longer than 5 characters.
|
|
321
|
+
If integer, abbreviate labels longer than `abbr` characters.
|
|
322
|
+
:param maxwidth: maximum number of characters before a label starts to
|
|
323
|
+
wrap; pass None to disable.
|
|
324
|
+
"""
|
|
325
|
+
if abbreviate == True:
|
|
326
|
+
abbreviate = 5
|
|
327
|
+
if unicodelines:
|
|
328
|
+
horzline = '\u2500'
|
|
329
|
+
leftcorner = '\u250c'
|
|
330
|
+
rightcorner = '\u2510'
|
|
331
|
+
vertline = ' \u2502 '
|
|
332
|
+
tee = horzline + '\u252C' + horzline
|
|
333
|
+
bottom = horzline + '\u2534' + horzline
|
|
334
|
+
cross = horzline + '\u253c' + horzline
|
|
335
|
+
ellipsis = '\u2026'
|
|
336
|
+
else:
|
|
337
|
+
horzline = '_'
|
|
338
|
+
leftcorner = rightcorner = ' '
|
|
339
|
+
vertline = ' | '
|
|
340
|
+
tee = 3 * horzline
|
|
341
|
+
cross = bottom = '_|_'
|
|
342
|
+
ellipsis = '.'
|
|
343
|
+
|
|
344
|
+
def crosscell(cur, x=vertline):
|
|
345
|
+
"""Overwrite center of this cell with a vertical branch."""
|
|
346
|
+
splitl = len(cur) - len(cur) // 2 - len(x) // 2 - 1
|
|
347
|
+
lst = list(cur)
|
|
348
|
+
lst[splitl:splitl + len(x)] = list(x)
|
|
349
|
+
return ''.join(lst)
|
|
350
|
+
|
|
351
|
+
result = []
|
|
352
|
+
matrix = defaultdict(dict)
|
|
353
|
+
maxnodewith = defaultdict(lambda: 3)
|
|
354
|
+
maxnodeheight = defaultdict(lambda: 1)
|
|
355
|
+
maxcol = 0
|
|
356
|
+
minchildcol = {}
|
|
357
|
+
maxchildcol = {}
|
|
358
|
+
childcols = defaultdict(set)
|
|
359
|
+
labels = {}
|
|
360
|
+
wrapre = re.compile('(.{%d,%d}\\b\\W*|.{%d})' % (
|
|
361
|
+
maxwidth - 4, maxwidth, maxwidth))
|
|
362
|
+
# collect labels and coordinates
|
|
363
|
+
for a in self.nodes:
|
|
364
|
+
row, column = self.coords[a]
|
|
365
|
+
matrix[row][column] = a
|
|
366
|
+
maxcol = max(maxcol, column)
|
|
367
|
+
label = (self.nodes[a].label() if isinstance(self.nodes[a], Tree)
|
|
368
|
+
else self.nodes[a])
|
|
369
|
+
if abbreviate and len(label) > abbreviate:
|
|
370
|
+
label = label[:abbreviate] + ellipsis
|
|
371
|
+
if maxwidth and len(label) > maxwidth:
|
|
372
|
+
label = wrapre.sub(r'\1\n', label).strip()
|
|
373
|
+
label = label.split('\n')
|
|
374
|
+
maxnodeheight[row] = max(maxnodeheight[row], len(label))
|
|
375
|
+
maxnodewith[column] = max(maxnodewith[column], max(map(len, label)))
|
|
376
|
+
labels[a] = label
|
|
377
|
+
if a not in self.edges:
|
|
378
|
+
continue # e.g., root
|
|
379
|
+
parent = self.edges[a]
|
|
380
|
+
childcols[parent].add((row, column))
|
|
381
|
+
minchildcol[parent] = min(minchildcol.get(parent, column), column)
|
|
382
|
+
maxchildcol[parent] = max(maxchildcol.get(parent, column), column)
|
|
383
|
+
# bottom up level order traversal
|
|
384
|
+
for row in sorted(matrix, reverse=True):
|
|
385
|
+
noderows = [[''.center(maxnodewith[col]) for col in range(maxcol + 1)]
|
|
386
|
+
for _ in range(maxnodeheight[row])]
|
|
387
|
+
branchrow = [''.center(maxnodewith[col]) for col in range(maxcol + 1)]
|
|
388
|
+
for col in matrix[row]:
|
|
389
|
+
n = matrix[row][col]
|
|
390
|
+
node = self.nodes[n]
|
|
391
|
+
text = labels[n]
|
|
392
|
+
if isinstance(node, Tree):
|
|
393
|
+
# draw horizontal branch towards children for this node
|
|
394
|
+
if n in minchildcol and minchildcol[n] < maxchildcol[n]:
|
|
395
|
+
i, j = minchildcol[n], maxchildcol[n]
|
|
396
|
+
a, b = (maxnodewith[i] + 1) // 2 - 1, maxnodewith[j] // 2
|
|
397
|
+
branchrow[i] = ((' ' * a) + leftcorner).ljust(
|
|
398
|
+
maxnodewith[i], horzline)
|
|
399
|
+
branchrow[j] = (rightcorner + (' ' * b)).rjust(
|
|
400
|
+
maxnodewith[j], horzline)
|
|
401
|
+
for i in range(minchildcol[n] + 1, maxchildcol[n]):
|
|
402
|
+
if i == col and any(
|
|
403
|
+
a == i for _, a in childcols[n]):
|
|
404
|
+
line = cross
|
|
405
|
+
elif i == col:
|
|
406
|
+
line = bottom
|
|
407
|
+
elif any(a == i for _, a in childcols[n]):
|
|
408
|
+
line = tee
|
|
409
|
+
else:
|
|
410
|
+
line = horzline
|
|
411
|
+
branchrow[i] = line.center(maxnodewith[i], horzline)
|
|
412
|
+
else: # if n and n in minchildcol:
|
|
413
|
+
branchrow[col] = crosscell(branchrow[col])
|
|
414
|
+
text = [a.center(maxnodewith[col]) for a in text]
|
|
415
|
+
color = nodecolor if isinstance(node, Tree) else leafcolor
|
|
416
|
+
if isinstance(node, Tree) and node.label().startswith('-'):
|
|
417
|
+
color = funccolor
|
|
418
|
+
if html:
|
|
419
|
+
text = [escape(a) for a in text]
|
|
420
|
+
if n in self.highlight:
|
|
421
|
+
text = ['<font color=%s>%s</font>' % (
|
|
422
|
+
color, a) for a in text]
|
|
423
|
+
elif ansi and n in self.highlight:
|
|
424
|
+
text = ['\x1b[%d;1m%s\x1b[0m' % (
|
|
425
|
+
ANSICOLOR[color], a) for a in text]
|
|
426
|
+
for x in range(maxnodeheight[row]):
|
|
427
|
+
# draw vertical lines in partially filled multiline node
|
|
428
|
+
# labels, but only if it's not a frontier node.
|
|
429
|
+
noderows[x][col] = (text[x] if x < len(text)
|
|
430
|
+
else (vertline if childcols[n] else ' ').center(
|
|
431
|
+
maxnodewith[col], ' '))
|
|
432
|
+
# for each column, if there is a node below us which has a parent
|
|
433
|
+
# above us, draw a vertical branch in that column.
|
|
434
|
+
if row != max(matrix):
|
|
435
|
+
for n, (childrow, col) in self.coords.items():
|
|
436
|
+
if (n > 0 and
|
|
437
|
+
self.coords[self.edges[n]][0] < row < childrow):
|
|
438
|
+
branchrow[col] = crosscell(branchrow[col])
|
|
439
|
+
if col not in matrix[row]:
|
|
440
|
+
for noderow in noderows:
|
|
441
|
+
noderow[col] = crosscell(noderow[col])
|
|
442
|
+
branchrow = [a + ((a[-1] if a[-1] != ' ' else b[0]) * nodedist)
|
|
443
|
+
for a, b in zip(branchrow, branchrow[1:] + [' '])]
|
|
444
|
+
result.append(''.join(branchrow))
|
|
445
|
+
result.extend((' ' * nodedist).join(noderow)
|
|
446
|
+
for noderow in reversed(noderows))
|
|
447
|
+
return '\n'.join(reversed(result)) + '\n'
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def svg(self, nodecolor='blue', leafcolor='red', funccolor='green'):
|
|
451
|
+
"""
|
|
452
|
+
:return: SVG representation of a tree.
|
|
453
|
+
"""
|
|
454
|
+
fontsize = 12
|
|
455
|
+
hscale = 40
|
|
456
|
+
vscale = 25
|
|
457
|
+
hstart = vstart = 20
|
|
458
|
+
width = max(col for _, col in self.coords.values())
|
|
459
|
+
height = max(row for row, _ in self.coords.values())
|
|
460
|
+
result = ['<svg version="1.1" xmlns="http://www.w3.org/2000/svg" '
|
|
461
|
+
'width="%dem" height="%dem" viewBox="%d %d %d %d">' % (
|
|
462
|
+
width * 3,
|
|
463
|
+
height * 2.5,
|
|
464
|
+
-hstart, -vstart,
|
|
465
|
+
width * hscale + 3 * hstart,
|
|
466
|
+
height * vscale + 3 * vstart)
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
children = defaultdict(set)
|
|
470
|
+
for n in self.nodes:
|
|
471
|
+
if n:
|
|
472
|
+
children[self.edges[n]].add(n)
|
|
473
|
+
|
|
474
|
+
# horizontal branches from nodes to children
|
|
475
|
+
for node in self.nodes:
|
|
476
|
+
if not children[node]:
|
|
477
|
+
continue
|
|
478
|
+
y, x = self.coords[node]
|
|
479
|
+
x *= hscale
|
|
480
|
+
y *= vscale
|
|
481
|
+
x += hstart
|
|
482
|
+
y += vstart + fontsize // 2
|
|
483
|
+
childx = [self.coords[c][1] for c in children[node]]
|
|
484
|
+
xmin = hstart + hscale * min(childx)
|
|
485
|
+
xmax = hstart + hscale * max(childx)
|
|
486
|
+
result.append(
|
|
487
|
+
'\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
|
|
488
|
+
'points="%g,%g %g,%g" />' % (xmin, y, xmax, y))
|
|
489
|
+
result.append(
|
|
490
|
+
'\t<polyline style="stroke:black; stroke-width:1; fill:none;" '
|
|
491
|
+
'points="%g,%g %g,%g" />' % (x, y, x, y - fontsize // 3))
|
|
492
|
+
|
|
493
|
+
# vertical branches from children to parents
|
|
494
|
+
for child, parent in self.edges.items():
|
|
495
|
+
y, _ = self.coords[parent]
|
|
496
|
+
y *= vscale
|
|
497
|
+
y += vstart + fontsize // 2
|
|
498
|
+
childy, childx = self.coords[child]
|
|
499
|
+
childx *= hscale
|
|
500
|
+
childy *= vscale
|
|
501
|
+
childx += hstart
|
|
502
|
+
childy += vstart - fontsize
|
|
503
|
+
result += [
|
|
504
|
+
'\t<polyline style="stroke:white; stroke-width:10; fill:none;"'
|
|
505
|
+
' points="%g,%g %g,%g" />' % (childx, childy, childx, y + 5),
|
|
506
|
+
'\t<polyline style="stroke:black; stroke-width:1; fill:none;"'
|
|
507
|
+
' points="%g,%g %g,%g" />' % (childx, childy, childx, y),
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
# write nodes with coordinates
|
|
511
|
+
for n, (row, column) in self.coords.items():
|
|
512
|
+
node = self.nodes[n]
|
|
513
|
+
x = column * hscale + hstart
|
|
514
|
+
y = row * vscale + vstart
|
|
515
|
+
if n in self.highlight:
|
|
516
|
+
color = nodecolor if isinstance(node, Tree) else leafcolor
|
|
517
|
+
if isinstance(node, Tree) and node.label().startswith('-'):
|
|
518
|
+
color = funccolor
|
|
519
|
+
else:
|
|
520
|
+
color = 'black'
|
|
521
|
+
result += ['\t<text style="text-anchor: middle; fill: %s; '
|
|
522
|
+
'font-size: %dpx;" x="%g" y="%g">%s</text>' % (
|
|
523
|
+
color, fontsize, x, y,
|
|
524
|
+
escape(node.label() if isinstance(node, Tree)
|
|
525
|
+
else node))]
|
|
526
|
+
|
|
527
|
+
result += ['</svg>']
|
|
528
|
+
return '\n'.join(result)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test():
|
|
532
|
+
"""Do some tree drawing tests."""
|
|
533
|
+
def print_tree(n, tree, sentence=None, ansi=True, **xargs):
|
|
534
|
+
print()
|
|
535
|
+
print('{0}: "{1}"'.format(n, ' '.join(sentence or tree.leaves())))
|
|
536
|
+
print(tree)
|
|
537
|
+
print()
|
|
538
|
+
drawtree = TreePrettyPrinter(tree, sentence)
|
|
539
|
+
try:
|
|
540
|
+
print(drawtree.text(unicodelines=ansi, ansi=ansi, **xargs))
|
|
541
|
+
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
542
|
+
print(drawtree.text(unicodelines=False, ansi=False, **xargs))
|
|
543
|
+
|
|
544
|
+
from nltk.corpus import treebank
|
|
545
|
+
for n in [0, 1440, 1591, 2771, 2170]:
|
|
546
|
+
tree = treebank.parsed_sents()[n]
|
|
547
|
+
print_tree(n, tree, nodedist=2, maxwidth=8)
|
|
548
|
+
print()
|
|
549
|
+
print('ASCII version:')
|
|
550
|
+
print(TreePrettyPrinter(tree).text(nodedist=2))
|
|
551
|
+
|
|
552
|
+
tree = Tree.fromstring(
|
|
553
|
+
'(top (punct 8) (smain (noun 0) (verb 1) (inf (verb 5) (inf (verb 6) '
|
|
554
|
+
'(conj (inf (pp (prep 2) (np (det 3) (noun 4))) (verb 7)) (inf (verb 9)) '
|
|
555
|
+
'(vg 10) (inf (verb 11)))))) (punct 12))', read_leaf=int)
|
|
556
|
+
sentence = ('Ze had met haar moeder kunnen gaan winkelen ,'
|
|
557
|
+
' zwemmen of terrassen .'.split())
|
|
558
|
+
print_tree('Discontinuous tree', tree, sentence, nodedist=2)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
__all__ = ['TreePrettyPrinter']
|
|
562
|
+
|
|
563
|
+
if __name__ == '__main__':
|
|
564
|
+
test()
|