framenet.py 128 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450
  1. # Natural Language Toolkit: Framenet Corpus Reader
  2. #
  3. # Copyright (C) 2001-2020 NLTK Project
  4. # Authors: Chuck Wooters <wooters@icsi.berkeley.edu>,
  5. # Nathan Schneider <nathan.schneider@georgetown.edu>
  6. # URL: <http://nltk.org/>
  7. # For license information, see LICENSE.TXT
  8. """
  9. Corpus reader for the FrameNet 1.7 lexicon and corpus.
  10. """
  11. import os
  12. import re
  13. import textwrap
  14. import itertools
  15. import sys
  16. import types
  17. from collections import defaultdict, OrderedDict
  18. from operator import itemgetter
  19. from itertools import zip_longest
  20. from pprint import pprint
  21. from nltk.corpus.reader import XMLCorpusReader, XMLCorpusView
  22. from nltk.util import LazyConcatenation, LazyMap, LazyIteratorList
  23. __docformat__ = "epytext en"
  24. def mimic_wrap(lines, wrap_at=65, **kwargs):
  25. """
  26. Wrap the first of 'lines' with textwrap and the remaining lines at exactly the same
  27. positions as the first.
  28. """
  29. l0 = textwrap.fill(lines[0], wrap_at, drop_whitespace=False).split("\n")
  30. yield l0
  31. def _(line):
  32. il0 = 0
  33. while line and il0 < len(l0) - 1:
  34. yield line[: len(l0[il0])]
  35. line = line[len(l0[il0]) :]
  36. il0 += 1
  37. if line: # Remaining stuff on this line past the end of the mimicked line.
  38. # So just textwrap this line.
  39. for ln in textwrap.fill(line, wrap_at, drop_whitespace=False).split("\n"):
  40. yield ln
  41. for l in lines[1:]:
  42. yield list(_(l))
  43. def _pretty_longstring(defstr, prefix="", wrap_at=65):
  44. """
  45. Helper function for pretty-printing a long string.
  46. :param defstr: The string to be printed.
  47. :type defstr: str
  48. :return: A nicely formated string representation of the long string.
  49. :rtype: str
  50. """
  51. outstr = ""
  52. for line in textwrap.fill(defstr, wrap_at).split("\n"):
  53. outstr += prefix + line + "\n"
  54. return outstr
  55. def _pretty_any(obj):
  56. """
  57. Helper function for pretty-printing any AttrDict object.
  58. :param obj: The obj to be printed.
  59. :type obj: AttrDict
  60. :return: A nicely formated string representation of the AttrDict object.
  61. :rtype: str
  62. """
  63. outstr = ""
  64. for k in obj:
  65. if isinstance(obj[k], str) and len(obj[k]) > 65:
  66. outstr += "[{0}]\n".format(k)
  67. outstr += "{0}".format(_pretty_longstring(obj[k], prefix=" "))
  68. outstr += "\n"
  69. else:
  70. outstr += "[{0}] {1}\n".format(k, obj[k])
  71. return outstr
  72. def _pretty_semtype(st):
  73. """
  74. Helper function for pretty-printing a semantic type.
  75. :param st: The semantic type to be printed.
  76. :type st: AttrDict
  77. :return: A nicely formated string representation of the semantic type.
  78. :rtype: str
  79. """
  80. semkeys = st.keys()
  81. if len(semkeys) == 1:
  82. return "<None>"
  83. outstr = ""
  84. outstr += "semantic type ({0.ID}): {0.name}\n".format(st)
  85. if "abbrev" in semkeys:
  86. outstr += "[abbrev] {0}\n".format(st.abbrev)
  87. if "definition" in semkeys:
  88. outstr += "[definition]\n"
  89. outstr += _pretty_longstring(st.definition, " ")
  90. outstr += "[rootType] {0}({1})\n".format(st.rootType.name, st.rootType.ID)
  91. if st.superType is None:
  92. outstr += "[superType] <None>\n"
  93. else:
  94. outstr += "[superType] {0}({1})\n".format(st.superType.name, st.superType.ID)
  95. outstr += "[subTypes] {0} subtypes\n".format(len(st.subTypes))
  96. outstr += (
  97. " "
  98. + ", ".join("{0}({1})".format(x.name, x.ID) for x in st.subTypes)
  99. + "\n" * (len(st.subTypes) > 0)
  100. )
  101. return outstr
  102. def _pretty_frame_relation_type(freltyp):
  103. """
  104. Helper function for pretty-printing a frame relation type.
  105. :param freltyp: The frame relation type to be printed.
  106. :type freltyp: AttrDict
  107. :return: A nicely formated string representation of the frame relation type.
  108. :rtype: str
  109. """
  110. outstr = "<frame relation type ({0.ID}): {0.superFrameName} -- {0.name} -> {0.subFrameName}>".format(
  111. freltyp
  112. )
  113. return outstr
  114. def _pretty_frame_relation(frel):
  115. """
  116. Helper function for pretty-printing a frame relation.
  117. :param frel: The frame relation to be printed.
  118. :type frel: AttrDict
  119. :return: A nicely formated string representation of the frame relation.
  120. :rtype: str
  121. """
  122. outstr = "<{0.type.superFrameName}={0.superFrameName} -- {0.type.name} -> {0.type.subFrameName}={0.subFrameName}>".format(
  123. frel
  124. )
  125. return outstr
  126. def _pretty_fe_relation(ferel):
  127. """
  128. Helper function for pretty-printing an FE relation.
  129. :param ferel: The FE relation to be printed.
  130. :type ferel: AttrDict
  131. :return: A nicely formated string representation of the FE relation.
  132. :rtype: str
  133. """
  134. outstr = "<{0.type.superFrameName}={0.frameRelation.superFrameName}.{0.superFEName} -- {0.type.name} -> {0.type.subFrameName}={0.frameRelation.subFrameName}.{0.subFEName}>".format(
  135. ferel
  136. )
  137. return outstr
  138. def _pretty_lu(lu):
  139. """
  140. Helper function for pretty-printing a lexical unit.
  141. :param lu: The lu to be printed.
  142. :type lu: AttrDict
  143. :return: A nicely formated string representation of the lexical unit.
  144. :rtype: str
  145. """
  146. lukeys = lu.keys()
  147. outstr = ""
  148. outstr += "lexical unit ({0.ID}): {0.name}\n\n".format(lu)
  149. if "definition" in lukeys:
  150. outstr += "[definition]\n"
  151. outstr += _pretty_longstring(lu.definition, " ")
  152. if "frame" in lukeys:
  153. outstr += "\n[frame] {0}({1})\n".format(lu.frame.name, lu.frame.ID)
  154. if "incorporatedFE" in lukeys:
  155. outstr += "\n[incorporatedFE] {0}\n".format(lu.incorporatedFE)
  156. if "POS" in lukeys:
  157. outstr += "\n[POS] {0}\n".format(lu.POS)
  158. if "status" in lukeys:
  159. outstr += "\n[status] {0}\n".format(lu.status)
  160. if "totalAnnotated" in lukeys:
  161. outstr += "\n[totalAnnotated] {0} annotated examples\n".format(
  162. lu.totalAnnotated
  163. )
  164. if "lexemes" in lukeys:
  165. outstr += "\n[lexemes] {0}\n".format(
  166. " ".join("{0}/{1}".format(lex.name, lex.POS) for lex in lu.lexemes)
  167. )
  168. if "semTypes" in lukeys:
  169. outstr += "\n[semTypes] {0} semantic types\n".format(len(lu.semTypes))
  170. outstr += (
  171. " " * (len(lu.semTypes) > 0)
  172. + ", ".join("{0}({1})".format(x.name, x.ID) for x in lu.semTypes)
  173. + "\n" * (len(lu.semTypes) > 0)
  174. )
  175. if "URL" in lukeys:
  176. outstr += "\n[URL] {0}\n".format(lu.URL)
  177. if "subCorpus" in lukeys:
  178. subc = [x.name for x in lu.subCorpus]
  179. outstr += "\n[subCorpus] {0} subcorpora\n".format(len(lu.subCorpus))
  180. for line in textwrap.fill(", ".join(sorted(subc)), 60).split("\n"):
  181. outstr += " {0}\n".format(line)
  182. if "exemplars" in lukeys:
  183. outstr += "\n[exemplars] {0} sentences across all subcorpora\n".format(
  184. len(lu.exemplars)
  185. )
  186. return outstr
  187. def _pretty_exemplars(exemplars, lu):
  188. """
  189. Helper function for pretty-printing a list of exemplar sentences for a lexical unit.
  190. :param sent: The list of exemplar sentences to be printed.
  191. :type sent: list(AttrDict)
  192. :return: An index of the text of the exemplar sentences.
  193. :rtype: str
  194. """
  195. outstr = ""
  196. outstr += "exemplar sentences for {0.name} in {0.frame.name}:\n\n".format(lu)
  197. for i, sent in enumerate(exemplars):
  198. outstr += "[{0}] {1}\n".format(i, sent.text)
  199. outstr += "\n"
  200. return outstr
  201. def _pretty_fulltext_sentences(sents):
  202. """
  203. Helper function for pretty-printing a list of annotated sentences for a full-text document.
  204. :param sent: The list of sentences to be printed.
  205. :type sent: list(AttrDict)
  206. :return: An index of the text of the sentences.
  207. :rtype: str
  208. """
  209. outstr = ""
  210. outstr += "full-text document ({0.ID}) {0.name}:\n\n".format(sents)
  211. outstr += "[corpid] {0.corpid}\n[corpname] {0.corpname}\n[description] {0.description}\n[URL] {0.URL}\n\n".format(
  212. sents
  213. )
  214. outstr += "[sentence]\n".format(sents)
  215. for i, sent in enumerate(sents.sentence):
  216. outstr += "[{0}] {1}\n".format(i, sent.text)
  217. outstr += "\n"
  218. return outstr
  219. def _pretty_fulltext_sentence(sent):
  220. """
  221. Helper function for pretty-printing an annotated sentence from a full-text document.
  222. :param sent: The sentence to be printed.
  223. :type sent: list(AttrDict)
  224. :return: The text of the sentence with annotation set indices on frame targets.
  225. :rtype: str
  226. """
  227. outstr = ""
  228. outstr += "full-text sentence ({0.ID}) in {1}:\n\n".format(
  229. sent, sent.doc.get("name", sent.doc.description)
  230. )
  231. outstr += "\n[POS] {0} tags\n".format(len(sent.POS))
  232. outstr += "\n[POS_tagset] {0}\n\n".format(sent.POS_tagset)
  233. outstr += "[text] + [annotationSet]\n\n"
  234. outstr += sent._ascii() # -> _annotation_ascii()
  235. outstr += "\n"
  236. return outstr
  237. def _pretty_pos(aset):
  238. """
  239. Helper function for pretty-printing a sentence with its POS tags.
  240. :param aset: The POS annotation set of the sentence to be printed.
  241. :type sent: list(AttrDict)
  242. :return: The text of the sentence and its POS tags.
  243. :rtype: str
  244. """
  245. outstr = ""
  246. outstr += "POS annotation set ({0.ID}) {0.POS_tagset} in sentence {0.sent.ID}:\n\n".format(
  247. aset
  248. )
  249. # list the target spans and their associated aset index
  250. overt = sorted(aset.POS)
  251. sent = aset.sent
  252. s0 = sent.text
  253. s1 = ""
  254. s2 = ""
  255. i = 0
  256. adjust = 0
  257. for j, k, lbl in overt:
  258. assert j >= i, ("Overlapping targets?", (j, k, lbl))
  259. s1 += " " * (j - i) + "-" * (k - j)
  260. if len(lbl) > (k - j):
  261. # add space in the sentence to make room for the annotation index
  262. amt = len(lbl) - (k - j)
  263. s0 = (
  264. s0[: k + adjust] + "~" * amt + s0[k + adjust :]
  265. ) # '~' to prevent line wrapping
  266. s1 = s1[: k + adjust] + " " * amt + s1[k + adjust :]
  267. adjust += amt
  268. s2 += " " * (j - i) + lbl.ljust(k - j)
  269. i = k
  270. long_lines = [s0, s1, s2]
  271. outstr += "\n\n".join(
  272. map("\n".join, zip_longest(*mimic_wrap(long_lines), fillvalue=" "))
  273. ).replace("~", " ")
  274. outstr += "\n"
  275. return outstr
  276. def _pretty_annotation(sent, aset_level=False):
  277. """
  278. Helper function for pretty-printing an exemplar sentence for a lexical unit.
  279. :param sent: An annotation set or exemplar sentence to be printed.
  280. :param aset_level: If True, 'sent' is actually an annotation set within a sentence.
  281. :type sent: AttrDict
  282. :return: A nicely formated string representation of the exemplar sentence
  283. with its target, frame, and FE annotations.
  284. :rtype: str
  285. """
  286. sentkeys = sent.keys()
  287. outstr = "annotation set" if aset_level else "exemplar sentence"
  288. outstr += " ({0.ID}):\n".format(sent)
  289. if aset_level: # TODO: any UNANN exemplars?
  290. outstr += "\n[status] {0}\n".format(sent.status)
  291. for k in ("corpID", "docID", "paragNo", "sentNo", "aPos"):
  292. if k in sentkeys:
  293. outstr += "[{0}] {1}\n".format(k, sent[k])
  294. outstr += (
  295. "\n[LU] ({0.ID}) {0.name} in {0.frame.name}\n".format(sent.LU)
  296. if sent.LU
  297. else "\n[LU] Not found!"
  298. )
  299. outstr += "\n[frame] ({0.ID}) {0.name}\n".format(
  300. sent.frame
  301. ) # redundant with above, but .frame is convenient
  302. if not aset_level:
  303. outstr += "\n[annotationSet] {0} annotation sets\n".format(
  304. len(sent.annotationSet)
  305. )
  306. outstr += "\n[POS] {0} tags\n".format(len(sent.POS))
  307. outstr += "\n[POS_tagset] {0}\n".format(sent.POS_tagset)
  308. outstr += "\n[GF] {0} relation{1}\n".format(
  309. len(sent.GF), "s" if len(sent.GF) != 1 else ""
  310. )
  311. outstr += "\n[PT] {0} phrase{1}\n".format(
  312. len(sent.PT), "s" if len(sent.PT) != 1 else ""
  313. )
  314. """
  315. Special Layers
  316. --------------
  317. The 'NER' layer contains, for some of the data, named entity labels.
  318. The 'WSL' (word status layer) contains, for some of the data,
  319. spans which should not in principle be considered targets (NT).
  320. The 'Other' layer records relative clause constructions (Rel=relativizer, Ant=antecedent),
  321. pleonastic 'it' (Null), and existential 'there' (Exist).
  322. On occasion they are duplicated by accident (e.g., annotationSet 1467275 in lu6700.xml).
  323. The 'Sent' layer appears to contain labels that the annotator has flagged the
  324. sentence with for their convenience: values include
  325. 'sense1', 'sense2', 'sense3', etc.;
  326. 'Blend', 'Canonical', 'Idiom', 'Metaphor', 'Special-Sent',
  327. 'keepS', 'deleteS', 'reexamine'
  328. (sometimes they are duplicated for no apparent reason).
  329. The POS-specific layers may contain the following kinds of spans:
  330. Asp (aspectual particle), Non-Asp (non-aspectual particle),
  331. Cop (copula), Supp (support), Ctrlr (controller),
  332. Gov (governor), X. Gov and X always cooccur.
  333. >>> from nltk.corpus import framenet as fn
  334. >>> def f(luRE, lyr, ignore=set()):
  335. ... for i,ex in enumerate(fn.exemplars(luRE)):
  336. ... if lyr in ex and ex[lyr] and set(zip(*ex[lyr])[2]) - ignore:
  337. ... print(i,ex[lyr])
  338. - Verb: Asp, Non-Asp
  339. - Noun: Cop, Supp, Ctrlr, Gov, X
  340. - Adj: Cop, Supp, Ctrlr, Gov, X
  341. - Prep: Cop, Supp, Ctrlr
  342. - Adv: Ctrlr
  343. - Scon: (none)
  344. - Art: (none)
  345. """
  346. for lyr in ("NER", "WSL", "Other", "Sent"):
  347. if lyr in sent and sent[lyr]:
  348. outstr += "\n[{0}] {1} entr{2}\n".format(
  349. lyr, len(sent[lyr]), "ies" if len(sent[lyr]) != 1 else "y"
  350. )
  351. outstr += "\n[text] + [Target] + [FE]"
  352. # POS-specific layers: syntactically important words that are neither the target
  353. # nor the FEs. Include these along with the first FE layer but with '^' underlining.
  354. for lyr in ("Verb", "Noun", "Adj", "Adv", "Prep", "Scon", "Art"):
  355. if lyr in sent and sent[lyr]:
  356. outstr += " + [{0}]".format(lyr)
  357. if "FE2" in sentkeys:
  358. outstr += " + [FE2]"
  359. if "FE3" in sentkeys:
  360. outstr += " + [FE3]"
  361. outstr += "\n\n"
  362. outstr += sent._ascii() # -> _annotation_ascii()
  363. outstr += "\n"
  364. return outstr
  365. def _annotation_ascii(sent):
  366. """
  367. Given a sentence or FE annotation set, construct the width-limited string showing
  368. an ASCII visualization of the sentence's annotations, calling either
  369. _annotation_ascii_frames() or _annotation_ascii_FEs() as appropriate.
  370. This will be attached as a method to appropriate AttrDict instances
  371. and called in the full pretty-printing of the instance.
  372. """
  373. if sent._type == "fulltext_sentence" or (
  374. "annotationSet" in sent and len(sent.annotationSet) > 2
  375. ):
  376. # a full-text sentence OR sentence with multiple targets.
  377. # (multiple targets = >2 annotation sets, because the first annotation set is POS.)
  378. return _annotation_ascii_frames(sent)
  379. else: # an FE annotation set, or an LU sentence with 1 target
  380. return _annotation_ascii_FEs(sent)
  381. def _annotation_ascii_frames(sent):
  382. """
  383. ASCII string rendering of the sentence along with its targets and frame names.
  384. Called for all full-text sentences, as well as the few LU sentences with multiple
  385. targets (e.g., fn.lu(6412).exemplars[82] has two want.v targets).
  386. Line-wrapped to limit the display width.
  387. """
  388. # list the target spans and their associated aset index
  389. overt = []
  390. for a, aset in enumerate(sent.annotationSet[1:]):
  391. for j, k in aset.Target:
  392. indexS = "[{0}]".format(a + 1)
  393. if aset.status == "UNANN" or aset.LU.status == "Problem":
  394. indexS += " "
  395. if aset.status == "UNANN":
  396. indexS += (
  397. "!"
  398. ) # warning indicator that there is a frame annotation but no FE annotation
  399. if aset.LU.status == "Problem":
  400. indexS += (
  401. "?"
  402. ) # warning indicator that there is a missing LU definition (because the LU has Problem status)
  403. overt.append((j, k, aset.LU.frame.name, indexS))
  404. overt = sorted(overt)
  405. duplicates = set()
  406. for o, (j, k, fname, asetIndex) in enumerate(overt):
  407. if o > 0 and j <= overt[o - 1][1]:
  408. # multiple annotation sets on the same target
  409. # (e.g. due to a coordination construction or multiple annotators)
  410. if (
  411. overt[o - 1][:2] == (j, k) and overt[o - 1][2] == fname
  412. ): # same target, same frame
  413. # splice indices together
  414. combinedIndex = (
  415. overt[o - 1][3] + asetIndex
  416. ) # e.g., '[1][2]', '[1]! [2]'
  417. combinedIndex = combinedIndex.replace(" !", "! ").replace(" ?", "? ")
  418. overt[o - 1] = overt[o - 1][:3] + (combinedIndex,)
  419. duplicates.add(o)
  420. else: # different frames, same or overlapping targets
  421. s = sent.text
  422. for j, k, fname, asetIndex in overt:
  423. s += "\n" + asetIndex + " " + sent.text[j:k] + " :: " + fname
  424. s += "\n(Unable to display sentence with targets marked inline due to overlap)"
  425. return s
  426. for o in reversed(sorted(duplicates)):
  427. del overt[o]
  428. s0 = sent.text
  429. s1 = ""
  430. s11 = ""
  431. s2 = ""
  432. i = 0
  433. adjust = 0
  434. fAbbrevs = OrderedDict()
  435. for j, k, fname, asetIndex in overt:
  436. if not j >= i:
  437. assert j >= i, (
  438. "Overlapping targets?"
  439. + (
  440. " UNANN"
  441. if any(aset.status == "UNANN" for aset in sent.annotationSet[1:])
  442. else ""
  443. ),
  444. (j, k, asetIndex),
  445. )
  446. s1 += " " * (j - i) + "*" * (k - j)
  447. short = fname[: k - j]
  448. if (k - j) < len(fname):
  449. r = 0
  450. while short in fAbbrevs:
  451. if fAbbrevs[short] == fname:
  452. break
  453. r += 1
  454. short = fname[: k - j - 1] + str(r)
  455. else: # short not in fAbbrevs
  456. fAbbrevs[short] = fname
  457. s11 += " " * (j - i) + short.ljust(k - j)
  458. if len(asetIndex) > (k - j):
  459. # add space in the sentence to make room for the annotation index
  460. amt = len(asetIndex) - (k - j)
  461. s0 = (
  462. s0[: k + adjust] + "~" * amt + s0[k + adjust :]
  463. ) # '~' to prevent line wrapping
  464. s1 = s1[: k + adjust] + " " * amt + s1[k + adjust :]
  465. s11 = s11[: k + adjust] + " " * amt + s11[k + adjust :]
  466. adjust += amt
  467. s2 += " " * (j - i) + asetIndex.ljust(k - j)
  468. i = k
  469. long_lines = [s0, s1, s11, s2]
  470. outstr = "\n\n".join(
  471. map("\n".join, zip_longest(*mimic_wrap(long_lines), fillvalue=" "))
  472. ).replace("~", " ")
  473. outstr += "\n"
  474. if fAbbrevs:
  475. outstr += " (" + ", ".join("=".join(pair) for pair in fAbbrevs.items()) + ")"
  476. assert len(fAbbrevs) == len(dict(fAbbrevs)), "Abbreviation clash"
  477. return outstr
  478. def _annotation_ascii_FE_layer(overt, ni, feAbbrevs):
  479. """Helper for _annotation_ascii_FEs()."""
  480. s1 = ""
  481. s2 = ""
  482. i = 0
  483. for j, k, fename in overt:
  484. s1 += " " * (j - i) + ("^" if fename.islower() else "-") * (k - j)
  485. short = fename[: k - j]
  486. if len(fename) > len(short):
  487. r = 0
  488. while short in feAbbrevs:
  489. if feAbbrevs[short] == fename:
  490. break
  491. r += 1
  492. short = fename[: k - j - 1] + str(r)
  493. else: # short not in feAbbrevs
  494. feAbbrevs[short] = fename
  495. s2 += " " * (j - i) + short.ljust(k - j)
  496. i = k
  497. sNI = ""
  498. if ni:
  499. sNI += " [" + ", ".join(":".join(x) for x in sorted(ni.items())) + "]"
  500. return [s1, s2, sNI]
  501. def _annotation_ascii_FEs(sent):
  502. """
  503. ASCII string rendering of the sentence along with a single target and its FEs.
  504. Secondary and tertiary FE layers are included if present.
  505. 'sent' can be an FE annotation set or an LU sentence with a single target.
  506. Line-wrapped to limit the display width.
  507. """
  508. feAbbrevs = OrderedDict()
  509. posspec = [] # POS-specific layer spans (e.g., Supp[ort], Cop[ula])
  510. posspec_separate = False
  511. for lyr in ("Verb", "Noun", "Adj", "Adv", "Prep", "Scon", "Art"):
  512. if lyr in sent and sent[lyr]:
  513. for a, b, lbl in sent[lyr]:
  514. if (
  515. lbl == "X"
  516. ): # skip this, which covers an entire phrase typically containing the target and all its FEs
  517. # (but do display the Gov)
  518. continue
  519. if any(1 for x, y, felbl in sent.FE[0] if x <= a < y or a <= x < b):
  520. # overlap between one of the POS-specific layers and first FE layer
  521. posspec_separate = (
  522. True
  523. ) # show POS-specific layers on a separate line
  524. posspec.append(
  525. (a, b, lbl.lower().replace("-", ""))
  526. ) # lowercase Cop=>cop, Non-Asp=>nonasp, etc. to distinguish from FE names
  527. if posspec_separate:
  528. POSSPEC = _annotation_ascii_FE_layer(posspec, {}, feAbbrevs)
  529. FE1 = _annotation_ascii_FE_layer(
  530. sorted(sent.FE[0] + (posspec if not posspec_separate else [])),
  531. sent.FE[1],
  532. feAbbrevs,
  533. )
  534. FE2 = FE3 = None
  535. if "FE2" in sent:
  536. FE2 = _annotation_ascii_FE_layer(sent.FE2[0], sent.FE2[1], feAbbrevs)
  537. if "FE3" in sent:
  538. FE3 = _annotation_ascii_FE_layer(sent.FE3[0], sent.FE3[1], feAbbrevs)
  539. for i, j in sent.Target:
  540. FE1span, FE1name, FE1exp = FE1
  541. if len(FE1span) < j:
  542. FE1span += " " * (j - len(FE1span))
  543. if len(FE1name) < j:
  544. FE1name += " " * (j - len(FE1name))
  545. FE1[1] = FE1name
  546. FE1[0] = (
  547. FE1span[:i] + FE1span[i:j].replace(" ", "*").replace("-", "=") + FE1span[j:]
  548. )
  549. long_lines = [sent.text]
  550. if posspec_separate:
  551. long_lines.extend(POSSPEC[:2])
  552. long_lines.extend([FE1[0], FE1[1] + FE1[2]]) # lines with no length limit
  553. if FE2:
  554. long_lines.extend([FE2[0], FE2[1] + FE2[2]])
  555. if FE3:
  556. long_lines.extend([FE3[0], FE3[1] + FE3[2]])
  557. long_lines.append("")
  558. outstr = "\n".join(
  559. map("\n".join, zip_longest(*mimic_wrap(long_lines), fillvalue=" "))
  560. )
  561. if feAbbrevs:
  562. outstr += "(" + ", ".join("=".join(pair) for pair in feAbbrevs.items()) + ")"
  563. assert len(feAbbrevs) == len(dict(feAbbrevs)), "Abbreviation clash"
  564. outstr += "\n"
  565. return outstr
  566. def _pretty_fe(fe):
  567. """
  568. Helper function for pretty-printing a frame element.
  569. :param fe: The frame element to be printed.
  570. :type fe: AttrDict
  571. :return: A nicely formated string representation of the frame element.
  572. :rtype: str
  573. """
  574. fekeys = fe.keys()
  575. outstr = ""
  576. outstr += "frame element ({0.ID}): {0.name}\n of {1.name}({1.ID})\n".format(
  577. fe, fe.frame
  578. )
  579. if "definition" in fekeys:
  580. outstr += "[definition]\n"
  581. outstr += _pretty_longstring(fe.definition, " ")
  582. if "abbrev" in fekeys:
  583. outstr += "[abbrev] {0}\n".format(fe.abbrev)
  584. if "coreType" in fekeys:
  585. outstr += "[coreType] {0}\n".format(fe.coreType)
  586. if "requiresFE" in fekeys:
  587. outstr += "[requiresFE] "
  588. if fe.requiresFE is None:
  589. outstr += "<None>\n"
  590. else:
  591. outstr += "{0}({1})\n".format(fe.requiresFE.name, fe.requiresFE.ID)
  592. if "excludesFE" in fekeys:
  593. outstr += "[excludesFE] "
  594. if fe.excludesFE is None:
  595. outstr += "<None>\n"
  596. else:
  597. outstr += "{0}({1})\n".format(fe.excludesFE.name, fe.excludesFE.ID)
  598. if "semType" in fekeys:
  599. outstr += "[semType] "
  600. if fe.semType is None:
  601. outstr += "<None>\n"
  602. else:
  603. outstr += "\n " + "{0}({1})".format(fe.semType.name, fe.semType.ID) + "\n"
  604. return outstr
  605. def _pretty_frame(frame):
  606. """
  607. Helper function for pretty-printing a frame.
  608. :param frame: The frame to be printed.
  609. :type frame: AttrDict
  610. :return: A nicely formated string representation of the frame.
  611. :rtype: str
  612. """
  613. outstr = ""
  614. outstr += "frame ({0.ID}): {0.name}\n\n".format(frame)
  615. outstr += "[URL] {0}\n\n".format(frame.URL)
  616. outstr += "[definition]\n"
  617. outstr += _pretty_longstring(frame.definition, " ") + "\n"
  618. outstr += "[semTypes] {0} semantic types\n".format(len(frame.semTypes))
  619. outstr += (
  620. " " * (len(frame.semTypes) > 0)
  621. + ", ".join("{0}({1})".format(x.name, x.ID) for x in frame.semTypes)
  622. + "\n" * (len(frame.semTypes) > 0)
  623. )
  624. outstr += "\n[frameRelations] {0} frame relations\n".format(
  625. len(frame.frameRelations)
  626. )
  627. outstr += " " + "\n ".join(repr(frel) for frel in frame.frameRelations) + "\n"
  628. outstr += "\n[lexUnit] {0} lexical units\n".format(len(frame.lexUnit))
  629. lustrs = []
  630. for luName, lu in sorted(frame.lexUnit.items()):
  631. tmpstr = "{0} ({1})".format(luName, lu.ID)
  632. lustrs.append(tmpstr)
  633. outstr += "{0}\n".format(_pretty_longstring(", ".join(lustrs), prefix=" "))
  634. outstr += "\n[FE] {0} frame elements\n".format(len(frame.FE))
  635. fes = {}
  636. for feName, fe in sorted(frame.FE.items()):
  637. try:
  638. fes[fe.coreType].append("{0} ({1})".format(feName, fe.ID))
  639. except KeyError:
  640. fes[fe.coreType] = []
  641. fes[fe.coreType].append("{0} ({1})".format(feName, fe.ID))
  642. for ct in sorted(
  643. fes.keys(),
  644. key=lambda ct2: [
  645. "Core",
  646. "Core-Unexpressed",
  647. "Peripheral",
  648. "Extra-Thematic",
  649. ].index(ct2),
  650. ):
  651. outstr += "{0:>16}: {1}\n".format(ct, ", ".join(sorted(fes[ct])))
  652. outstr += "\n[FEcoreSets] {0} frame element core sets\n".format(
  653. len(frame.FEcoreSets)
  654. )
  655. outstr += (
  656. " "
  657. + "\n ".join(
  658. ", ".join([x.name for x in coreSet]) for coreSet in frame.FEcoreSets
  659. )
  660. + "\n"
  661. )
  662. return outstr
  663. class FramenetError(Exception):
  664. """An exception class for framenet-related errors."""
  665. class AttrDict(dict):
  666. """A class that wraps a dict and allows accessing the keys of the
  667. dict as if they were attributes. Taken from here:
  668. http://stackoverflow.com/a/14620633/8879
  669. >>> foo = {'a':1, 'b':2, 'c':3}
  670. >>> bar = AttrDict(foo)
  671. >>> pprint(dict(bar))
  672. {'a': 1, 'b': 2, 'c': 3}
  673. >>> bar.b
  674. 2
  675. >>> bar.d = 4
  676. >>> pprint(dict(bar))
  677. {'a': 1, 'b': 2, 'c': 3, 'd': 4}
  678. """
  679. def __init__(self, *args, **kwargs):
  680. super(AttrDict, self).__init__(*args, **kwargs)
  681. # self.__dict__ = self
  682. def __setattr__(self, name, value):
  683. self[name] = value
  684. def __getattr__(self, name):
  685. if name == "_short_repr":
  686. return self._short_repr
  687. return self[name]
  688. def __getitem__(self, name):
  689. v = super(AttrDict, self).__getitem__(name)
  690. if isinstance(v, Future):
  691. return v._data()
  692. return v
  693. def _short_repr(self):
  694. if "_type" in self:
  695. if self["_type"].endswith("relation"):
  696. return self.__repr__()
  697. try:
  698. return "<{0} ID={1} name={2}>".format(
  699. self["_type"], self["ID"], self["name"]
  700. )
  701. except KeyError:
  702. try: # no ID--e.g., for _type=lusubcorpus
  703. return "<{0} name={1}>".format(self["_type"], self["name"])
  704. except KeyError: # no name--e.g., for _type=lusentence
  705. return "<{0} ID={1}>".format(self["_type"], self["ID"])
  706. else:
  707. return self.__repr__()
  708. def _str(self):
  709. outstr = ""
  710. if "_type" not in self:
  711. outstr = _pretty_any(self)
  712. elif self["_type"] == "frame":
  713. outstr = _pretty_frame(self)
  714. elif self["_type"] == "fe":
  715. outstr = _pretty_fe(self)
  716. elif self["_type"] == "lu":
  717. outstr = _pretty_lu(self)
  718. elif self["_type"] == "luexemplars": # list of ALL exemplars for LU
  719. outstr = _pretty_exemplars(self, self[0].LU)
  720. elif (
  721. self["_type"] == "fulltext_annotation"
  722. ): # list of all sentences for full-text doc
  723. outstr = _pretty_fulltext_sentences(self)
  724. elif self["_type"] == "lusentence":
  725. outstr = _pretty_annotation(self)
  726. elif self["_type"] == "fulltext_sentence":
  727. outstr = _pretty_fulltext_sentence(self)
  728. elif self["_type"] in ("luannotationset", "fulltext_annotationset"):
  729. outstr = _pretty_annotation(self, aset_level=True)
  730. elif self["_type"] == "posannotationset":
  731. outstr = _pretty_pos(self)
  732. elif self["_type"] == "semtype":
  733. outstr = _pretty_semtype(self)
  734. elif self["_type"] == "framerelationtype":
  735. outstr = _pretty_frame_relation_type(self)
  736. elif self["_type"] == "framerelation":
  737. outstr = _pretty_frame_relation(self)
  738. elif self["_type"] == "ferelation":
  739. outstr = _pretty_fe_relation(self)
  740. else:
  741. outstr = _pretty_any(self)
  742. # ensure result is unicode string prior to applying the
  743. # decorator (because non-ASCII characters
  744. # could in principle occur in the data and would trigger an encoding error when
  745. # passed as arguments to str.format()).
  746. # assert isinstance(outstr, unicode) # not in Python 3.2
  747. return outstr
  748. def __str__(self):
  749. return self._str()
  750. def __repr__(self):
  751. return self.__str__()
  752. class SpecialList(list):
  753. """
  754. A list subclass which adds a '_type' attribute for special printing
  755. (similar to an AttrDict, though this is NOT an AttrDict subclass).
  756. """
  757. def __init__(self, typ, *args, **kwargs):
  758. super(SpecialList, self).__init__(*args, **kwargs)
  759. self._type = typ
  760. def _str(self):
  761. outstr = ""
  762. assert self._type
  763. if len(self) == 0:
  764. outstr = "[]"
  765. elif self._type == "luexemplars": # list of ALL exemplars for LU
  766. outstr = _pretty_exemplars(self, self[0].LU)
  767. else:
  768. assert False, self._type
  769. return outstr
  770. def __str__(self):
  771. return self._str()
  772. def __repr__(self):
  773. return self.__str__()
  774. class Future(object):
  775. """
  776. Wraps and acts as a proxy for a value to be loaded lazily (on demand).
  777. Adapted from https://gist.github.com/sergey-miryanov/2935416
  778. """
  779. def __init__(self, loader, *args, **kwargs):
  780. """
  781. :param loader: when called with no arguments, returns the value to be stored
  782. :type loader: callable
  783. """
  784. super(Future, self).__init__(*args, **kwargs)
  785. self._loader = loader
  786. self._d = None
  787. def _data(self):
  788. if callable(self._loader):
  789. self._d = self._loader()
  790. self._loader = None # the data is now cached
  791. return self._d
  792. def __nonzero__(self):
  793. return bool(self._data())
  794. def __len__(self):
  795. return len(self._data())
  796. def __setitem__(self, key, value):
  797. return self._data().__setitem__(key, value)
  798. def __getitem__(self, key):
  799. return self._data().__getitem__(key)
  800. def __getattr__(self, key):
  801. return self._data().__getattr__(key)
  802. def __str__(self):
  803. return self._data().__str__()
  804. def __repr__(self):
  805. return self._data().__repr__()
  806. class PrettyDict(AttrDict):
  807. """
  808. Displays an abbreviated repr of values where possible.
  809. Inherits from AttrDict, so a callable value will
  810. be lazily converted to an actual value.
  811. """
  812. def __init__(self, *args, **kwargs):
  813. _BREAK_LINES = kwargs.pop("breakLines", False)
  814. super(PrettyDict, self).__init__(*args, **kwargs)
  815. dict.__setattr__(self, "_BREAK_LINES", _BREAK_LINES)
  816. def __repr__(self):
  817. parts = []
  818. for k, v in sorted(self.items()):
  819. kv = repr(k) + ": "
  820. try:
  821. kv += v._short_repr()
  822. except AttributeError:
  823. kv += repr(v)
  824. parts.append(kv)
  825. return "{" + (",\n " if self._BREAK_LINES else ", ").join(parts) + "}"
  826. class PrettyList(list):
  827. """
  828. Displays an abbreviated repr of only the first several elements, not the whole list.
  829. """
  830. # from nltk.util
  831. def __init__(self, *args, **kwargs):
  832. self._MAX_REPR_SIZE = kwargs.pop("maxReprSize", 60)
  833. self._BREAK_LINES = kwargs.pop("breakLines", False)
  834. super(PrettyList, self).__init__(*args, **kwargs)
  835. def __repr__(self):
  836. """
  837. Return a string representation for this corpus view that is
  838. similar to a list's representation; but if it would be more
  839. than 60 characters long, it is truncated.
  840. """
  841. pieces = []
  842. length = 5
  843. for elt in self:
  844. pieces.append(
  845. elt._short_repr()
  846. ) # key difference from inherited version: call to _short_repr()
  847. length += len(pieces[-1]) + 2
  848. if self._MAX_REPR_SIZE and length > self._MAX_REPR_SIZE and len(pieces) > 2:
  849. return "[%s, ...]" % str(
  850. ",\n " if self._BREAK_LINES else ", "
  851. ).join(pieces[:-1])
  852. return "[%s]" % str(",\n " if self._BREAK_LINES else ", ").join(pieces)
  853. class PrettyLazyMap(LazyMap):
  854. """
  855. Displays an abbreviated repr of only the first several elements, not the whole list.
  856. """
  857. # from nltk.util
  858. _MAX_REPR_SIZE = 60
  859. def __repr__(self):
  860. """
  861. Return a string representation for this corpus view that is
  862. similar to a list's representation; but if it would be more
  863. than 60 characters long, it is truncated.
  864. """
  865. pieces = []
  866. length = 5
  867. for elt in self:
  868. pieces.append(
  869. elt._short_repr()
  870. ) # key difference from inherited version: call to _short_repr()
  871. length += len(pieces[-1]) + 2
  872. if length > self._MAX_REPR_SIZE and len(pieces) > 2:
  873. return "[%s, ...]" % str(", ").join(pieces[:-1])
  874. return "[%s]" % str(", ").join(pieces)
  875. class PrettyLazyIteratorList(LazyIteratorList):
  876. """
  877. Displays an abbreviated repr of only the first several elements, not the whole list.
  878. """
  879. # from nltk.util
  880. _MAX_REPR_SIZE = 60
  881. def __repr__(self):
  882. """
  883. Return a string representation for this corpus view that is
  884. similar to a list's representation; but if it would be more
  885. than 60 characters long, it is truncated.
  886. """
  887. pieces = []
  888. length = 5
  889. for elt in self:
  890. pieces.append(
  891. elt._short_repr()
  892. ) # key difference from inherited version: call to _short_repr()
  893. length += len(pieces[-1]) + 2
  894. if length > self._MAX_REPR_SIZE and len(pieces) > 2:
  895. return "[%s, ...]" % str(", ").join(pieces[:-1])
  896. return "[%s]" % str(", ").join(pieces)
  897. class PrettyLazyConcatenation(LazyConcatenation):
  898. """
  899. Displays an abbreviated repr of only the first several elements, not the whole list.
  900. """
  901. # from nltk.util
  902. _MAX_REPR_SIZE = 60
  903. def __repr__(self):
  904. """
  905. Return a string representation for this corpus view that is
  906. similar to a list's representation; but if it would be more
  907. than 60 characters long, it is truncated.
  908. """
  909. pieces = []
  910. length = 5
  911. for elt in self:
  912. pieces.append(
  913. elt._short_repr()
  914. ) # key difference from inherited version: call to _short_repr()
  915. length += len(pieces[-1]) + 2
  916. if length > self._MAX_REPR_SIZE and len(pieces) > 2:
  917. return "[%s, ...]" % str(", ").join(pieces[:-1])
  918. return "[%s]" % str(", ").join(pieces)
  919. def __add__(self, other):
  920. """Return a list concatenating self with other."""
  921. return PrettyLazyIteratorList(itertools.chain(self, other))
  922. def __radd__(self, other):
  923. """Return a list concatenating other with self."""
  924. return PrettyLazyIteratorList(itertools.chain(other, self))
  925. class FramenetCorpusReader(XMLCorpusReader):
  926. """A corpus reader for the Framenet Corpus.
  927. >>> from nltk.corpus import framenet as fn
  928. >>> fn.lu(3238).frame.lexUnit['glint.v'] is fn.lu(3238)
  929. True
  930. >>> fn.frame_by_name('Replacing') is fn.lus('replace.v')[0].frame
  931. True
  932. >>> fn.lus('prejudice.n')[0].frame.frameRelations == fn.frame_relations('Partiality')
  933. True
  934. """
  935. _bad_statuses = ["Problem"]
  936. """
  937. When loading LUs for a frame, those whose status is in this list will be ignored.
  938. Due to caching, if user code modifies this, it should do so before loading any data.
  939. 'Problem' should always be listed for FrameNet 1.5, as these LUs are not included
  940. in the XML index.
  941. """
  942. _warnings = False
  943. def warnings(self, v):
  944. """Enable or disable warnings of data integrity issues as they are encountered.
  945. If v is truthy, warnings will be enabled.
  946. (This is a function rather than just an attribute/property to ensure that if
  947. enabling warnings is the first action taken, the corpus reader is instantiated first.)
  948. """
  949. self._warnings = v
  950. def __init__(self, root, fileids):
  951. XMLCorpusReader.__init__(self, root, fileids)
  952. # framenet corpus sub dirs
  953. # sub dir containing the xml files for frames
  954. self._frame_dir = "frame"
  955. # sub dir containing the xml files for lexical units
  956. self._lu_dir = "lu"
  957. # sub dir containing the xml files for fulltext annotation files
  958. self._fulltext_dir = "fulltext"
  959. # location of latest development version of FrameNet
  960. self._fnweb_url = "https://framenet2.icsi.berkeley.edu/fnReports/data"
  961. # Indexes used for faster look-ups
  962. self._frame_idx = None
  963. self._cached_frames = {} # name -> ID
  964. self._lu_idx = None
  965. self._fulltext_idx = None
  966. self._semtypes = None
  967. self._freltyp_idx = None # frame relation types (Inheritance, Using, etc.)
  968. self._frel_idx = None # frame-to-frame relation instances
  969. self._ferel_idx = None # FE-to-FE relation instances
  970. self._frel_f_idx = None # frame-to-frame relations associated with each frame
  971. def help(self, attrname=None):
  972. """Display help information summarizing the main methods."""
  973. if attrname is not None:
  974. return help(self.__getattribute__(attrname))
  975. # No need to mention frame_by_name() or frame_by_id(),
  976. # as it's easier to just call frame().
  977. # Also not mentioning lu_basic().
  978. msg = """
  979. Citation: Nathan Schneider and Chuck Wooters (2017),
  980. "The NLTK FrameNet API: Designing for Discoverability with a Rich Linguistic Resource".
  981. Proceedings of EMNLP: System Demonstrations. https://arxiv.org/abs/1703.07438
  982. Use the following methods to access data in FrameNet.
  983. Provide a method name to `help()` for more information.
  984. FRAMES
  985. ======
  986. frame() to look up a frame by its exact name or ID
  987. frames() to get frames matching a name pattern
  988. frames_by_lemma() to get frames containing an LU matching a name pattern
  989. frame_ids_and_names() to get a mapping from frame IDs to names
  990. FRAME ELEMENTS
  991. ==============
  992. fes() to get frame elements (a.k.a. roles) matching a name pattern, optionally constrained
  993. by a frame name pattern
  994. LEXICAL UNITS
  995. =============
  996. lu() to look up an LU by its ID
  997. lus() to get lexical units matching a name pattern, optionally constrained by frame
  998. lu_ids_and_names() to get a mapping from LU IDs to names
  999. RELATIONS
  1000. =========
  1001. frame_relation_types() to get the different kinds of frame-to-frame relations
  1002. (Inheritance, Subframe, Using, etc.).
  1003. frame_relations() to get the relation instances, optionally constrained by
  1004. frame(s) or relation type
  1005. fe_relations() to get the frame element pairs belonging to a frame-to-frame relation
  1006. SEMANTIC TYPES
  1007. ==============
  1008. semtypes() to get the different kinds of semantic types that can be applied to
  1009. FEs, LUs, and entire frames
  1010. semtype() to look up a particular semtype by name, ID, or abbreviation
  1011. semtype_inherits() to check whether two semantic types have a subtype-supertype
  1012. relationship in the semtype hierarchy
  1013. propagate_semtypes() to apply inference rules that distribute semtypes over relations
  1014. between FEs
  1015. ANNOTATIONS
  1016. ===========
  1017. annotations() to get annotation sets, in which a token in a sentence is annotated
  1018. with a lexical unit in a frame, along with its frame elements and their syntactic properties;
  1019. can be constrained by LU name pattern and limited to lexicographic exemplars or full-text.
  1020. Sentences of full-text annotation can have multiple annotation sets.
  1021. sents() to get annotated sentences illustrating one or more lexical units
  1022. exemplars() to get sentences of lexicographic annotation, most of which have
  1023. just 1 annotation set; can be constrained by LU name pattern, frame, and overt FE(s)
  1024. doc() to look up a document of full-text annotation by its ID
  1025. docs() to get documents of full-text annotation that match a name pattern
  1026. docs_metadata() to get metadata about all full-text documents without loading them
  1027. ft_sents() to iterate over sentences of full-text annotation
  1028. UTILITIES
  1029. =========
  1030. buildindexes() loads metadata about all frames, LUs, etc. into memory to avoid
  1031. delay when one is accessed for the first time. It does not load annotations.
  1032. readme() gives the text of the FrameNet README file
  1033. warnings(True) to display corpus consistency warnings when loading data
  1034. """
  1035. print(msg)
  1036. def _buildframeindex(self):
  1037. # The total number of Frames in Framenet is fairly small (~1200) so
  1038. # this index should not be very large
  1039. if not self._frel_idx:
  1040. self._buildrelationindex() # always load frame relations before frames,
  1041. # otherwise weird ordering effects might result in incomplete information
  1042. self._frame_idx = {}
  1043. for f in XMLCorpusView(
  1044. self.abspath("frameIndex.xml"), "frameIndex/frame", self._handle_elt
  1045. ):
  1046. self._frame_idx[f["ID"]] = f
  1047. def _buildcorpusindex(self):
  1048. # The total number of fulltext annotated documents in Framenet
  1049. # is fairly small (~90) so this index should not be very large
  1050. self._fulltext_idx = {}
  1051. for doclist in XMLCorpusView(
  1052. self.abspath("fulltextIndex.xml"),
  1053. "fulltextIndex/corpus",
  1054. self._handle_fulltextindex_elt,
  1055. ):
  1056. for doc in doclist:
  1057. self._fulltext_idx[doc.ID] = doc
  1058. def _buildluindex(self):
  1059. # The number of LUs in Framenet is about 13,000 so this index
  1060. # should not be very large
  1061. self._lu_idx = {}
  1062. for lu in XMLCorpusView(
  1063. self.abspath("luIndex.xml"), "luIndex/lu", self._handle_elt
  1064. ):
  1065. self._lu_idx[
  1066. lu["ID"]
  1067. ] = lu # populate with LU index entries. if any of these
  1068. # are looked up they will be replaced by full LU objects.
  1069. def _buildrelationindex(self):
  1070. # print('building relation index...', file=sys.stderr)
  1071. freltypes = PrettyList(
  1072. x
  1073. for x in XMLCorpusView(
  1074. self.abspath("frRelation.xml"),
  1075. "frameRelations/frameRelationType",
  1076. self._handle_framerelationtype_elt,
  1077. )
  1078. )
  1079. self._freltyp_idx = {}
  1080. self._frel_idx = {}
  1081. self._frel_f_idx = defaultdict(set)
  1082. self._ferel_idx = {}
  1083. for freltyp in freltypes:
  1084. self._freltyp_idx[freltyp.ID] = freltyp
  1085. for frel in freltyp.frameRelations:
  1086. supF = frel.superFrame = frel[freltyp.superFrameName] = Future(
  1087. (lambda fID: lambda: self.frame_by_id(fID))(frel.supID)
  1088. )
  1089. subF = frel.subFrame = frel[freltyp.subFrameName] = Future(
  1090. (lambda fID: lambda: self.frame_by_id(fID))(frel.subID)
  1091. )
  1092. self._frel_idx[frel.ID] = frel
  1093. self._frel_f_idx[frel.supID].add(frel.ID)
  1094. self._frel_f_idx[frel.subID].add(frel.ID)
  1095. for ferel in frel.feRelations:
  1096. ferel.superFrame = supF
  1097. ferel.subFrame = subF
  1098. ferel.superFE = Future(
  1099. (lambda fer: lambda: fer.superFrame.FE[fer.superFEName])(ferel)
  1100. )
  1101. ferel.subFE = Future(
  1102. (lambda fer: lambda: fer.subFrame.FE[fer.subFEName])(ferel)
  1103. )
  1104. self._ferel_idx[ferel.ID] = ferel
  1105. # print('...done building relation index', file=sys.stderr)
  1106. def _warn(self, *message, **kwargs):
  1107. if self._warnings:
  1108. kwargs.setdefault("file", sys.stderr)
  1109. print(*message, **kwargs)
  1110. def readme(self):
  1111. """
  1112. Return the contents of the corpus README.txt (or README) file.
  1113. """
  1114. try:
  1115. return self.open("README.txt").read()
  1116. except IOError:
  1117. return self.open("README").read()
  1118. def buildindexes(self):
  1119. """
  1120. Build the internal indexes to make look-ups faster.
  1121. """
  1122. # Frames
  1123. self._buildframeindex()
  1124. # LUs
  1125. self._buildluindex()
  1126. # Fulltext annotation corpora index
  1127. self._buildcorpusindex()
  1128. # frame and FE relations
  1129. self._buildrelationindex()
  1130. def doc(self, fn_docid):
  1131. """
  1132. Returns the annotated document whose id number is
  1133. ``fn_docid``. This id number can be obtained by calling the
  1134. Documents() function.
  1135. The dict that is returned from this function will contain the
  1136. following keys:
  1137. - '_type' : 'fulltextannotation'
  1138. - 'sentence' : a list of sentences in the document
  1139. - Each item in the list is a dict containing the following keys:
  1140. - 'ID' : the ID number of the sentence
  1141. - '_type' : 'sentence'
  1142. - 'text' : the text of the sentence
  1143. - 'paragNo' : the paragraph number
  1144. - 'sentNo' : the sentence number
  1145. - 'docID' : the document ID number
  1146. - 'corpID' : the corpus ID number
  1147. - 'aPos' : the annotation position
  1148. - 'annotationSet' : a list of annotation layers for the sentence
  1149. - Each item in the list is a dict containing the following keys:
  1150. - 'ID' : the ID number of the annotation set
  1151. - '_type' : 'annotationset'
  1152. - 'status' : either 'MANUAL' or 'UNANN'
  1153. - 'luName' : (only if status is 'MANUAL')
  1154. - 'luID' : (only if status is 'MANUAL')
  1155. - 'frameID' : (only if status is 'MANUAL')
  1156. - 'frameName': (only if status is 'MANUAL')
  1157. - 'layer' : a list of labels for the layer
  1158. - Each item in the layer is a dict containing the
  1159. following keys:
  1160. - '_type': 'layer'
  1161. - 'rank'
  1162. - 'name'
  1163. - 'label' : a list of labels in the layer
  1164. - Each item is a dict containing the following keys:
  1165. - 'start'
  1166. - 'end'
  1167. - 'name'
  1168. - 'feID' (optional)
  1169. :param fn_docid: The Framenet id number of the document
  1170. :type fn_docid: int
  1171. :return: Information about the annotated document
  1172. :rtype: dict
  1173. """
  1174. try:
  1175. xmlfname = self._fulltext_idx[fn_docid].filename
  1176. except TypeError: # happens when self._fulltext_idx == None
  1177. # build the index
  1178. self._buildcorpusindex()
  1179. xmlfname = self._fulltext_idx[fn_docid].filename
  1180. except KeyError: # probably means that fn_docid was not in the index
  1181. raise FramenetError("Unknown document id: {0}".format(fn_docid))
  1182. # construct the path name for the xml file containing the document info
  1183. locpath = os.path.join("{0}".format(self._root), self._fulltext_dir, xmlfname)
  1184. # Grab the top-level xml element containing the fulltext annotation
  1185. elt = XMLCorpusView(locpath, "fullTextAnnotation")[0]
  1186. info = self._handle_fulltextannotation_elt(elt)
  1187. # add metadata
  1188. for k, v in self._fulltext_idx[fn_docid].items():
  1189. info[k] = v
  1190. return info
  1191. def frame_by_id(self, fn_fid, ignorekeys=[]):
  1192. """
  1193. Get the details for the specified Frame using the frame's id
  1194. number.
  1195. Usage examples:
  1196. >>> from nltk.corpus import framenet as fn
  1197. >>> f = fn.frame_by_id(256)
  1198. >>> f.ID
  1199. 256
  1200. >>> f.name
  1201. 'Medical_specialties'
  1202. >>> f.definition
  1203. "This frame includes words that name ..."
  1204. :param fn_fid: The Framenet id number of the frame
  1205. :type fn_fid: int
  1206. :param ignorekeys: The keys to ignore. These keys will not be
  1207. included in the output. (optional)
  1208. :type ignorekeys: list(str)
  1209. :return: Information about a frame
  1210. :rtype: dict
  1211. Also see the ``frame()`` function for details about what is
  1212. contained in the dict that is returned.
  1213. """
  1214. # get the name of the frame with this id number
  1215. try:
  1216. fentry = self._frame_idx[fn_fid]
  1217. if "_type" in fentry:
  1218. return fentry # full frame object is cached
  1219. name = fentry["name"]
  1220. except TypeError:
  1221. self._buildframeindex()
  1222. name = self._frame_idx[fn_fid]["name"]
  1223. except KeyError:
  1224. raise FramenetError("Unknown frame id: {0}".format(fn_fid))
  1225. return self.frame_by_name(name, ignorekeys, check_cache=False)
  1226. def frame_by_name(self, fn_fname, ignorekeys=[], check_cache=True):
  1227. """
  1228. Get the details for the specified Frame using the frame's name.
  1229. Usage examples:
  1230. >>> from nltk.corpus import framenet as fn
  1231. >>> f = fn.frame_by_name('Medical_specialties')
  1232. >>> f.ID
  1233. 256
  1234. >>> f.name
  1235. 'Medical_specialties'
  1236. >>> f.definition
  1237. "This frame includes words that name ..."
  1238. :param fn_fname: The name of the frame
  1239. :type fn_fname: str
  1240. :param ignorekeys: The keys to ignore. These keys will not be
  1241. included in the output. (optional)
  1242. :type ignorekeys: list(str)
  1243. :return: Information about a frame
  1244. :rtype: dict
  1245. Also see the ``frame()`` function for details about what is
  1246. contained in the dict that is returned.
  1247. """
  1248. if check_cache and fn_fname in self._cached_frames:
  1249. return self._frame_idx[self._cached_frames[fn_fname]]
  1250. elif not self._frame_idx:
  1251. self._buildframeindex()
  1252. # construct the path name for the xml file containing the Frame info
  1253. locpath = os.path.join(
  1254. "{0}".format(self._root), self._frame_dir, fn_fname + ".xml"
  1255. )
  1256. # print(locpath, file=sys.stderr)
  1257. # Grab the xml for the frame
  1258. try:
  1259. elt = XMLCorpusView(locpath, "frame")[0]
  1260. except IOError:
  1261. raise FramenetError("Unknown frame: {0}".format(fn_fname))
  1262. fentry = self._handle_frame_elt(elt, ignorekeys)
  1263. assert fentry
  1264. fentry.URL = self._fnweb_url + "/" + self._frame_dir + "/" + fn_fname + ".xml"
  1265. # INFERENCE RULE: propagate lexical semtypes from the frame to all its LUs
  1266. for st in fentry.semTypes:
  1267. if st.rootType.name == "Lexical_type":
  1268. for lu in fentry.lexUnit.values():
  1269. if not any(
  1270. x is st for x in lu.semTypes
  1271. ): # identity containment check
  1272. lu.semTypes.append(st)
  1273. self._frame_idx[fentry.ID] = fentry
  1274. self._cached_frames[fentry.name] = fentry.ID
  1275. """
  1276. # now set up callables to resolve the LU pointers lazily.
  1277. # (could also do this here--caching avoids infinite recursion.)
  1278. for luName,luinfo in fentry.lexUnit.items():
  1279. fentry.lexUnit[luName] = (lambda luID: Future(lambda: self.lu(luID)))(luinfo.ID)
  1280. """
  1281. return fentry
  1282. def frame(self, fn_fid_or_fname, ignorekeys=[]):
  1283. """
  1284. Get the details for the specified Frame using the frame's name
  1285. or id number.
  1286. Usage examples:
  1287. >>> from nltk.corpus import framenet as fn
  1288. >>> f = fn.frame(256)
  1289. >>> f.name
  1290. 'Medical_specialties'
  1291. >>> f = fn.frame('Medical_specialties')
  1292. >>> f.ID
  1293. 256
  1294. >>> # ensure non-ASCII character in definition doesn't trigger an encoding error:
  1295. >>> fn.frame('Imposing_obligation')
  1296. frame (1494): Imposing_obligation...
  1297. The dict that is returned from this function will contain the
  1298. following information about the Frame:
  1299. - 'name' : the name of the Frame (e.g. 'Birth', 'Apply_heat', etc.)
  1300. - 'definition' : textual definition of the Frame
  1301. - 'ID' : the internal ID number of the Frame
  1302. - 'semTypes' : a list of semantic types for this frame
  1303. - Each item in the list is a dict containing the following keys:
  1304. - 'name' : can be used with the semtype() function
  1305. - 'ID' : can be used with the semtype() function
  1306. - 'lexUnit' : a dict containing all of the LUs for this frame.
  1307. The keys in this dict are the names of the LUs and
  1308. the value for each key is itself a dict containing
  1309. info about the LU (see the lu() function for more info.)
  1310. - 'FE' : a dict containing the Frame Elements that are part of this frame
  1311. The keys in this dict are the names of the FEs (e.g. 'Body_system')
  1312. and the values are dicts containing the following keys
  1313. - 'definition' : The definition of the FE
  1314. - 'name' : The name of the FE e.g. 'Body_system'
  1315. - 'ID' : The id number
  1316. - '_type' : 'fe'
  1317. - 'abbrev' : Abbreviation e.g. 'bod'
  1318. - 'coreType' : one of "Core", "Peripheral", or "Extra-Thematic"
  1319. - 'semType' : if not None, a dict with the following two keys:
  1320. - 'name' : name of the semantic type. can be used with
  1321. the semtype() function
  1322. - 'ID' : id number of the semantic type. can be used with
  1323. the semtype() function
  1324. - 'requiresFE' : if not None, a dict with the following two keys:
  1325. - 'name' : the name of another FE in this frame
  1326. - 'ID' : the id of the other FE in this frame
  1327. - 'excludesFE' : if not None, a dict with the following two keys:
  1328. - 'name' : the name of another FE in this frame
  1329. - 'ID' : the id of the other FE in this frame
  1330. - 'frameRelation' : a list of objects describing frame relations
  1331. - 'FEcoreSets' : a list of Frame Element core sets for this frame
  1332. - Each item in the list is a list of FE objects
  1333. :param fn_fid_or_fname: The Framenet name or id number of the frame
  1334. :type fn_fid_or_fname: int or str
  1335. :param ignorekeys: The keys to ignore. These keys will not be
  1336. included in the output. (optional)
  1337. :type ignorekeys: list(str)
  1338. :return: Information about a frame
  1339. :rtype: dict
  1340. """
  1341. # get the frame info by name or id number
  1342. if isinstance(fn_fid_or_fname, str):
  1343. f = self.frame_by_name(fn_fid_or_fname, ignorekeys)
  1344. else:
  1345. f = self.frame_by_id(fn_fid_or_fname, ignorekeys)
  1346. return f
  1347. def frames_by_lemma(self, pat):
  1348. """
  1349. Returns a list of all frames that contain LUs in which the
  1350. ``name`` attribute of the LU matchs the given regular expression
  1351. ``pat``. Note that LU names are composed of "lemma.POS", where
  1352. the "lemma" part can be made up of either a single lexeme
  1353. (e.g. 'run') or multiple lexemes (e.g. 'a little').
  1354. Note: if you are going to be doing a lot of this type of
  1355. searching, you'd want to build an index that maps from lemmas to
  1356. frames because each time frames_by_lemma() is called, it has to
  1357. search through ALL of the frame XML files in the db.
  1358. >>> from nltk.corpus import framenet as fn
  1359. >>> from nltk.corpus.reader.framenet import PrettyList
  1360. >>> PrettyList(sorted(fn.frames_by_lemma(r'(?i)a little'), key=itemgetter('ID'))) # doctest: +ELLIPSIS
  1361. [<frame ID=189 name=Quanti...>, <frame ID=2001 name=Degree>]
  1362. :return: A list of frame objects.
  1363. :rtype: list(AttrDict)
  1364. """
  1365. return PrettyList(
  1366. f
  1367. for f in self.frames()
  1368. if any(re.search(pat, luName) for luName in f.lexUnit)
  1369. )
  1370. def lu_basic(self, fn_luid):
  1371. """
  1372. Returns basic information about the LU whose id is
  1373. ``fn_luid``. This is basically just a wrapper around the
  1374. ``lu()`` function with "subCorpus" info excluded.
  1375. >>> from nltk.corpus import framenet as fn
  1376. >>> lu = PrettyDict(fn.lu_basic(256), breakLines=True)
  1377. >>> # ellipses account for differences between FN 1.5 and 1.7
  1378. >>> lu # doctest: +ELLIPSIS
  1379. {'ID': 256,
  1380. 'POS': 'V',
  1381. 'URL': 'https://framenet2.icsi.berkeley.edu/fnReports/data/lu/lu256.xml',
  1382. '_type': 'lu',
  1383. 'cBy': ...,
  1384. 'cDate': '02/08/2001 01:27:50 PST Thu',
  1385. 'definition': 'COD: be aware of beforehand; predict.',
  1386. 'definitionMarkup': 'COD: be aware of beforehand; predict.',
  1387. 'frame': <frame ID=26 name=Expectation>,
  1388. 'lemmaID': 15082,
  1389. 'lexemes': [{'POS': 'V', 'breakBefore': 'false', 'headword': 'false', 'name': 'foresee', 'order': 1}],
  1390. 'name': 'foresee.v',
  1391. 'semTypes': [],
  1392. 'sentenceCount': {'annotated': ..., 'total': ...},
  1393. 'status': 'FN1_Sent'}
  1394. :param fn_luid: The id number of the desired LU
  1395. :type fn_luid: int
  1396. :return: Basic information about the lexical unit
  1397. :rtype: dict
  1398. """
  1399. return self.lu(fn_luid, ignorekeys=["subCorpus", "exemplars"])
  1400. def lu(self, fn_luid, ignorekeys=[], luName=None, frameID=None, frameName=None):
  1401. """
  1402. Access a lexical unit by its ID. luName, frameID, and frameName are used
  1403. only in the event that the LU does not have a file in the database
  1404. (which is the case for LUs with "Problem" status); in this case,
  1405. a placeholder LU is created which just contains its name, ID, and frame.
  1406. Usage examples:
  1407. >>> from nltk.corpus import framenet as fn
  1408. >>> fn.lu(256).name
  1409. 'foresee.v'
  1410. >>> fn.lu(256).definition
  1411. 'COD: be aware of beforehand; predict.'
  1412. >>> fn.lu(256).frame.name
  1413. 'Expectation'
  1414. >>> pprint(list(map(PrettyDict, fn.lu(256).lexemes)))
  1415. [{'POS': 'V', 'breakBefore': 'false', 'headword': 'false', 'name': 'foresee', 'order': 1}]
  1416. >>> fn.lu(227).exemplars[23]
  1417. exemplar sentence (352962):
  1418. [sentNo] 0
  1419. [aPos] 59699508
  1420. <BLANKLINE>
  1421. [LU] (227) guess.v in Coming_to_believe
  1422. <BLANKLINE>
  1423. [frame] (23) Coming_to_believe
  1424. <BLANKLINE>
  1425. [annotationSet] 2 annotation sets
  1426. <BLANKLINE>
  1427. [POS] 18 tags
  1428. <BLANKLINE>
  1429. [POS_tagset] BNC
  1430. <BLANKLINE>
  1431. [GF] 3 relations
  1432. <BLANKLINE>
  1433. [PT] 3 phrases
  1434. <BLANKLINE>
  1435. [Other] 1 entry
  1436. <BLANKLINE>
  1437. [text] + [Target] + [FE]
  1438. <BLANKLINE>
  1439. When he was inside the house , Culley noticed the characteristic
  1440. ------------------
  1441. Content
  1442. <BLANKLINE>
  1443. he would n't have guessed at .
  1444. -- ******* --
  1445. Co C1 [Evidence:INI]
  1446. (Co=Cognizer, C1=Content)
  1447. <BLANKLINE>
  1448. <BLANKLINE>
  1449. The dict that is returned from this function will contain most of the
  1450. following information about the LU. Note that some LUs do not contain
  1451. all of these pieces of information - particularly 'totalAnnotated' and
  1452. 'incorporatedFE' may be missing in some LUs:
  1453. - 'name' : the name of the LU (e.g. 'merger.n')
  1454. - 'definition' : textual definition of the LU
  1455. - 'ID' : the internal ID number of the LU
  1456. - '_type' : 'lu'
  1457. - 'status' : e.g. 'Created'
  1458. - 'frame' : Frame that this LU belongs to
  1459. - 'POS' : the part of speech of this LU (e.g. 'N')
  1460. - 'totalAnnotated' : total number of examples annotated with this LU
  1461. - 'incorporatedFE' : FE that incorporates this LU (e.g. 'Ailment')
  1462. - 'sentenceCount' : a dict with the following two keys:
  1463. - 'annotated': number of sentences annotated with this LU
  1464. - 'total' : total number of sentences with this LU
  1465. - 'lexemes' : a list of dicts describing the lemma of this LU.
  1466. Each dict in the list contains these keys:
  1467. - 'POS' : part of speech e.g. 'N'
  1468. - 'name' : either single-lexeme e.g. 'merger' or
  1469. multi-lexeme e.g. 'a little'
  1470. - 'order': the order of the lexeme in the lemma (starting from 1)
  1471. - 'headword': a boolean ('true' or 'false')
  1472. - 'breakBefore': Can this lexeme be separated from the previous lexeme?
  1473. Consider: "take over.v" as in:
  1474. Germany took over the Netherlands in 2 days.
  1475. Germany took the Netherlands over in 2 days.
  1476. In this case, 'breakBefore' would be "true" for the lexeme
  1477. "over". Contrast this with "take after.v" as in:
  1478. Mary takes after her grandmother.
  1479. *Mary takes her grandmother after.
  1480. In this case, 'breakBefore' would be "false" for the lexeme "after"
  1481. - 'lemmaID' : Can be used to connect lemmas in different LUs
  1482. - 'semTypes' : a list of semantic type objects for this LU
  1483. - 'subCorpus' : a list of subcorpora
  1484. - Each item in the list is a dict containing the following keys:
  1485. - 'name' :
  1486. - 'sentence' : a list of sentences in the subcorpus
  1487. - each item in the list is a dict with the following keys:
  1488. - 'ID':
  1489. - 'sentNo':
  1490. - 'text': the text of the sentence
  1491. - 'aPos':
  1492. - 'annotationSet': a list of annotation sets
  1493. - each item in the list is a dict with the following keys:
  1494. - 'ID':
  1495. - 'status':
  1496. - 'layer': a list of layers
  1497. - each layer is a dict containing the following keys:
  1498. - 'name': layer name (e.g. 'BNC')
  1499. - 'rank':
  1500. - 'label': a list of labels for the layer
  1501. - each label is a dict containing the following keys:
  1502. - 'start': start pos of label in sentence 'text' (0-based)
  1503. - 'end': end pos of label in sentence 'text' (0-based)
  1504. - 'name': name of label (e.g. 'NN1')
  1505. Under the hood, this implementation looks up the lexical unit information
  1506. in the *frame* definition file. That file does not contain
  1507. corpus annotations, so the LU files will be accessed on demand if those are
  1508. needed. In principle, valence patterns could be loaded here too,
  1509. though these are not currently supported.
  1510. :param fn_luid: The id number of the lexical unit
  1511. :type fn_luid: int
  1512. :param ignorekeys: The keys to ignore. These keys will not be
  1513. included in the output. (optional)
  1514. :type ignorekeys: list(str)
  1515. :return: All information about the lexical unit
  1516. :rtype: dict
  1517. """
  1518. # look for this LU in cache
  1519. if not self._lu_idx:
  1520. self._buildluindex()
  1521. OOV = object()
  1522. luinfo = self._lu_idx.get(fn_luid, OOV)
  1523. if luinfo is OOV:
  1524. # LU not in the index. We create a placeholder by falling back to
  1525. # luName, frameID, and frameName. However, this will not be listed
  1526. # among the LUs for its frame.
  1527. self._warn(
  1528. "LU ID not found: {0} ({1}) in {2} ({3})".format(
  1529. luName, fn_luid, frameName, frameID
  1530. )
  1531. )
  1532. luinfo = AttrDict(
  1533. {
  1534. "_type": "lu",
  1535. "ID": fn_luid,
  1536. "name": luName,
  1537. "frameID": frameID,
  1538. "status": "Problem",
  1539. }
  1540. )
  1541. f = self.frame_by_id(luinfo.frameID)
  1542. assert f.name == frameName, (f.name, frameName)
  1543. luinfo["frame"] = f
  1544. self._lu_idx[fn_luid] = luinfo
  1545. elif "_type" not in luinfo:
  1546. # we only have an index entry for the LU. loading the frame will replace this.
  1547. f = self.frame_by_id(luinfo.frameID)
  1548. luinfo = self._lu_idx[fn_luid]
  1549. if ignorekeys:
  1550. return AttrDict(
  1551. dict((k, v) for k, v in luinfo.items() if k not in ignorekeys)
  1552. )
  1553. return luinfo
  1554. def _lu_file(self, lu, ignorekeys=[]):
  1555. """
  1556. Augment the LU information that was loaded from the frame file
  1557. with additional information from the LU file.
  1558. """
  1559. fn_luid = lu.ID
  1560. fname = "lu{0}.xml".format(fn_luid)
  1561. locpath = os.path.join("{0}".format(self._root), self._lu_dir, fname)
  1562. # print(locpath, file=sys.stderr)
  1563. if not self._lu_idx:
  1564. self._buildluindex()
  1565. try:
  1566. elt = XMLCorpusView(locpath, "lexUnit")[0]
  1567. except IOError:
  1568. raise FramenetError("Unknown LU id: {0}".format(fn_luid))
  1569. lu2 = self._handle_lexunit_elt(elt, ignorekeys)
  1570. lu.URL = self._fnweb_url + "/" + self._lu_dir + "/" + fname
  1571. lu.subCorpus = lu2.subCorpus
  1572. lu.exemplars = SpecialList(
  1573. "luexemplars", [sent for subc in lu.subCorpus for sent in subc.sentence]
  1574. )
  1575. for sent in lu.exemplars:
  1576. sent["LU"] = lu
  1577. sent["frame"] = lu.frame
  1578. for aset in sent.annotationSet:
  1579. aset["LU"] = lu
  1580. aset["frame"] = lu.frame
  1581. return lu
  1582. def _loadsemtypes(self):
  1583. """Create the semantic types index."""
  1584. self._semtypes = AttrDict()
  1585. semtypeXML = [
  1586. x
  1587. for x in XMLCorpusView(
  1588. self.abspath("semTypes.xml"),
  1589. "semTypes/semType",
  1590. self._handle_semtype_elt,
  1591. )
  1592. ]
  1593. for st in semtypeXML:
  1594. n = st["name"]
  1595. a = st["abbrev"]
  1596. i = st["ID"]
  1597. # Both name and abbrev should be able to retrieve the
  1598. # ID. The ID will retrieve the semantic type dict itself.
  1599. self._semtypes[n] = i
  1600. self._semtypes[a] = i
  1601. self._semtypes[i] = st
  1602. # now that all individual semtype XML is loaded, we can link them together
  1603. roots = []
  1604. for st in self.semtypes():
  1605. if st.superType:
  1606. st.superType = self.semtype(st.superType.supID)
  1607. st.superType.subTypes.append(st)
  1608. else:
  1609. if st not in roots:
  1610. roots.append(st)
  1611. st.rootType = st
  1612. queue = list(roots)
  1613. assert queue
  1614. while queue:
  1615. st = queue.pop(0)
  1616. for child in st.subTypes:
  1617. child.rootType = st.rootType
  1618. queue.append(child)
  1619. # self.propagate_semtypes() # apply inferencing over FE relations
  1620. def propagate_semtypes(self):
  1621. """
  1622. Apply inference rules to distribute semtypes over relations between FEs.
  1623. For FrameNet 1.5, this results in 1011 semtypes being propagated.
  1624. (Not done by default because it requires loading all frame files,
  1625. which takes several seconds. If this needed to be fast, it could be rewritten
  1626. to traverse the neighboring relations on demand for each FE semtype.)
  1627. >>> from nltk.corpus import framenet as fn
  1628. >>> x = sum(1 for f in fn.frames() for fe in f.FE.values() if fe.semType)
  1629. >>> fn.propagate_semtypes()
  1630. >>> y = sum(1 for f in fn.frames() for fe in f.FE.values() if fe.semType)
  1631. >>> y-x > 1000
  1632. True
  1633. """
  1634. if not self._semtypes:
  1635. self._loadsemtypes()
  1636. if not self._ferel_idx:
  1637. self._buildrelationindex()
  1638. changed = True
  1639. i = 0
  1640. nPropagations = 0
  1641. while changed:
  1642. # make a pass and see if anything needs to be propagated
  1643. i += 1
  1644. changed = False
  1645. for ferel in self.fe_relations():
  1646. superST = ferel.superFE.semType
  1647. subST = ferel.subFE.semType
  1648. try:
  1649. if superST and superST is not subST:
  1650. # propagate downward
  1651. assert subST is None or self.semtype_inherits(subST, superST), (
  1652. superST.name,
  1653. ferel,
  1654. subST.name,
  1655. )
  1656. if subST is None:
  1657. ferel.subFE.semType = subST = superST
  1658. changed = True
  1659. nPropagations += 1
  1660. if (
  1661. ferel.type.name in ["Perspective_on", "Subframe", "Precedes"]
  1662. and subST
  1663. and subST is not superST
  1664. ):
  1665. # propagate upward
  1666. assert superST is None, (superST.name, ferel, subST.name)
  1667. ferel.superFE.semType = superST = subST
  1668. changed = True
  1669. nPropagations += 1
  1670. except AssertionError as ex:
  1671. # bug in the data! ignore
  1672. # print(ex, file=sys.stderr)
  1673. continue
  1674. # print(i, nPropagations, file=sys.stderr)
  1675. def semtype(self, key):
  1676. """
  1677. >>> from nltk.corpus import framenet as fn
  1678. >>> fn.semtype(233).name
  1679. 'Temperature'
  1680. >>> fn.semtype(233).abbrev
  1681. 'Temp'
  1682. >>> fn.semtype('Temperature').ID
  1683. 233
  1684. :param key: The name, abbreviation, or id number of the semantic type
  1685. :type key: string or int
  1686. :return: Information about a semantic type
  1687. :rtype: dict
  1688. """
  1689. if isinstance(key, int):
  1690. stid = key
  1691. else:
  1692. try:
  1693. stid = self._semtypes[key]
  1694. except TypeError:
  1695. self._loadsemtypes()
  1696. stid = self._semtypes[key]
  1697. try:
  1698. st = self._semtypes[stid]
  1699. except TypeError:
  1700. self._loadsemtypes()
  1701. st = self._semtypes[stid]
  1702. return st
  1703. def semtype_inherits(self, st, superST):
  1704. if not isinstance(st, dict):
  1705. st = self.semtype(st)
  1706. if not isinstance(superST, dict):
  1707. superST = self.semtype(superST)
  1708. par = st.superType
  1709. while par:
  1710. if par is superST:
  1711. return True
  1712. par = par.superType
  1713. return False
  1714. def frames(self, name=None):
  1715. """
  1716. Obtain details for a specific frame.
  1717. >>> from nltk.corpus import framenet as fn
  1718. >>> len(fn.frames()) in (1019, 1221) # FN 1.5 and 1.7, resp.
  1719. True
  1720. >>> x = PrettyList(fn.frames(r'(?i)crim'), maxReprSize=0, breakLines=True)
  1721. >>> x.sort(key=itemgetter('ID'))
  1722. >>> x
  1723. [<frame ID=200 name=Criminal_process>,
  1724. <frame ID=500 name=Criminal_investigation>,
  1725. <frame ID=692 name=Crime_scenario>,
  1726. <frame ID=700 name=Committing_crime>]
  1727. A brief intro to Frames (excerpted from "FrameNet II: Extended
  1728. Theory and Practice" by Ruppenhofer et. al., 2010):
  1729. A Frame is a script-like conceptual structure that describes a
  1730. particular type of situation, object, or event along with the
  1731. participants and props that are needed for that Frame. For
  1732. example, the "Apply_heat" frame describes a common situation
  1733. involving a Cook, some Food, and a Heating_Instrument, and is
  1734. evoked by words such as bake, blanch, boil, broil, brown,
  1735. simmer, steam, etc.
  1736. We call the roles of a Frame "frame elements" (FEs) and the
  1737. frame-evoking words are called "lexical units" (LUs).
  1738. FrameNet includes relations between Frames. Several types of
  1739. relations are defined, of which the most important are:
  1740. - Inheritance: An IS-A relation. The child frame is a subtype
  1741. of the parent frame, and each FE in the parent is bound to
  1742. a corresponding FE in the child. An example is the
  1743. "Revenge" frame which inherits from the
  1744. "Rewards_and_punishments" frame.
  1745. - Using: The child frame presupposes the parent frame as
  1746. background, e.g the "Speed" frame "uses" (or presupposes)
  1747. the "Motion" frame; however, not all parent FEs need to be
  1748. bound to child FEs.
  1749. - Subframe: The child frame is a subevent of a complex event
  1750. represented by the parent, e.g. the "Criminal_process" frame
  1751. has subframes of "Arrest", "Arraignment", "Trial", and
  1752. "Sentencing".
  1753. - Perspective_on: The child frame provides a particular
  1754. perspective on an un-perspectivized parent frame. A pair of
  1755. examples consists of the "Hiring" and "Get_a_job" frames,
  1756. which perspectivize the "Employment_start" frame from the
  1757. Employer's and the Employee's point of view, respectively.
  1758. :param name: A regular expression pattern used to match against
  1759. Frame names. If 'name' is None, then a list of all
  1760. Framenet Frames will be returned.
  1761. :type name: str
  1762. :return: A list of matching Frames (or all Frames).
  1763. :rtype: list(AttrDict)
  1764. """
  1765. try:
  1766. fIDs = list(self._frame_idx.keys())
  1767. except AttributeError:
  1768. self._buildframeindex()
  1769. fIDs = list(self._frame_idx.keys())
  1770. if name is not None:
  1771. return PrettyList(
  1772. self.frame(fID) for fID, finfo in self.frame_ids_and_names(name).items()
  1773. )
  1774. else:
  1775. return PrettyLazyMap(self.frame, fIDs)
  1776. def frame_ids_and_names(self, name=None):
  1777. """
  1778. Uses the frame index, which is much faster than looking up each frame definition
  1779. if only the names and IDs are needed.
  1780. """
  1781. if not self._frame_idx:
  1782. self._buildframeindex()
  1783. return dict(
  1784. (fID, finfo.name)
  1785. for fID, finfo in self._frame_idx.items()
  1786. if name is None or re.search(name, finfo.name) is not None
  1787. )
  1788. def fes(self, name=None, frame=None):
  1789. """
  1790. Lists frame element objects. If 'name' is provided, this is treated as
  1791. a case-insensitive regular expression to filter by frame name.
  1792. (Case-insensitivity is because casing of frame element names is not always
  1793. consistent across frames.) Specify 'frame' to filter by a frame name pattern,
  1794. ID, or object.
  1795. >>> from nltk.corpus import framenet as fn
  1796. >>> fn.fes('Noise_maker')
  1797. [<fe ID=6043 name=Noise_maker>]
  1798. >>> sorted([(fe.frame.name,fe.name) for fe in fn.fes('sound')])
  1799. [('Cause_to_make_noise', 'Sound_maker'), ('Make_noise', 'Sound'),
  1800. ('Make_noise', 'Sound_source'), ('Sound_movement', 'Location_of_sound_source'),
  1801. ('Sound_movement', 'Sound'), ('Sound_movement', 'Sound_source'),
  1802. ('Sounds', 'Component_sound'), ('Sounds', 'Location_of_sound_source'),
  1803. ('Sounds', 'Sound_source'), ('Vocalizations', 'Location_of_sound_source'),
  1804. ('Vocalizations', 'Sound_source')]
  1805. >>> sorted([(fe.frame.name,fe.name) for fe in fn.fes('sound',r'(?i)make_noise')])
  1806. [('Cause_to_make_noise', 'Sound_maker'),
  1807. ('Make_noise', 'Sound'),
  1808. ('Make_noise', 'Sound_source')]
  1809. >>> sorted(set(fe.name for fe in fn.fes('^sound')))
  1810. ['Sound', 'Sound_maker', 'Sound_source']
  1811. >>> len(fn.fes('^sound$'))
  1812. 2
  1813. :param name: A regular expression pattern used to match against
  1814. frame element names. If 'name' is None, then a list of all
  1815. frame elements will be returned.
  1816. :type name: str
  1817. :return: A list of matching frame elements
  1818. :rtype: list(AttrDict)
  1819. """
  1820. # what frames are we searching in?
  1821. if frame is not None:
  1822. if isinstance(frame, int):
  1823. frames = [self.frame(frame)]
  1824. elif isinstance(frame, str):
  1825. frames = self.frames(frame)
  1826. else:
  1827. frames = [frame]
  1828. else:
  1829. frames = self.frames()
  1830. return PrettyList(
  1831. fe
  1832. for f in frames
  1833. for fename, fe in f.FE.items()
  1834. if name is None or re.search(name, fename, re.I)
  1835. )
  1836. def lus(self, name=None, frame=None):
  1837. """
  1838. Obtain details for lexical units.
  1839. Optionally restrict by lexical unit name pattern, and/or to a certain frame
  1840. or frames whose name matches a pattern.
  1841. >>> from nltk.corpus import framenet as fn
  1842. >>> len(fn.lus()) in (11829, 13572) # FN 1.5 and 1.7, resp.
  1843. True
  1844. >>> PrettyList(sorted(fn.lus(r'(?i)a little'), key=itemgetter('ID')), maxReprSize=0, breakLines=True)
  1845. [<lu ID=14733 name=a little.n>,
  1846. <lu ID=14743 name=a little.adv>,
  1847. <lu ID=14744 name=a little bit.adv>]
  1848. >>> PrettyList(sorted(fn.lus(r'interest', r'(?i)stimulus'), key=itemgetter('ID')))
  1849. [<lu ID=14894 name=interested.a>, <lu ID=14920 name=interesting.a>]
  1850. A brief intro to Lexical Units (excerpted from "FrameNet II:
  1851. Extended Theory and Practice" by Ruppenhofer et. al., 2010):
  1852. A lexical unit (LU) is a pairing of a word with a meaning. For
  1853. example, the "Apply_heat" Frame describes a common situation
  1854. involving a Cook, some Food, and a Heating Instrument, and is
  1855. _evoked_ by words such as bake, blanch, boil, broil, brown,
  1856. simmer, steam, etc. These frame-evoking words are the LUs in the
  1857. Apply_heat frame. Each sense of a polysemous word is a different
  1858. LU.
  1859. We have used the word "word" in talking about LUs. The reality
  1860. is actually rather complex. When we say that the word "bake" is
  1861. polysemous, we mean that the lemma "bake.v" (which has the
  1862. word-forms "bake", "bakes", "baked", and "baking") is linked to
  1863. three different frames:
  1864. - Apply_heat: "Michelle baked the potatoes for 45 minutes."
  1865. - Cooking_creation: "Michelle baked her mother a cake for her birthday."
  1866. - Absorb_heat: "The potatoes have to bake for more than 30 minutes."
  1867. These constitute three different LUs, with different
  1868. definitions.
  1869. Multiword expressions such as "given name" and hyphenated words
  1870. like "shut-eye" can also be LUs. Idiomatic phrases such as
  1871. "middle of nowhere" and "give the slip (to)" are also defined as
  1872. LUs in the appropriate frames ("Isolated_places" and "Evading",
  1873. respectively), and their internal structure is not analyzed.
  1874. Framenet provides multiple annotated examples of each sense of a
  1875. word (i.e. each LU). Moreover, the set of examples
  1876. (approximately 20 per LU) illustrates all of the combinatorial
  1877. possibilities of the lexical unit.
  1878. Each LU is linked to a Frame, and hence to the other words which
  1879. evoke that Frame. This makes the FrameNet database similar to a
  1880. thesaurus, grouping together semantically similar words.
  1881. In the simplest case, frame-evoking words are verbs such as
  1882. "fried" in:
  1883. "Matilde fried the catfish in a heavy iron skillet."
  1884. Sometimes event nouns may evoke a Frame. For example,
  1885. "reduction" evokes "Cause_change_of_scalar_position" in:
  1886. "...the reduction of debt levels to $665 million from $2.6 billion."
  1887. Adjectives may also evoke a Frame. For example, "asleep" may
  1888. evoke the "Sleep" frame as in:
  1889. "They were asleep for hours."
  1890. Many common nouns, such as artifacts like "hat" or "tower",
  1891. typically serve as dependents rather than clearly evoking their
  1892. own frames.
  1893. :param name: A regular expression pattern used to search the LU
  1894. names. Note that LU names take the form of a dotted
  1895. string (e.g. "run.v" or "a little.adv") in which a
  1896. lemma preceeds the "." and a POS follows the
  1897. dot. The lemma may be composed of a single lexeme
  1898. (e.g. "run") or of multiple lexemes (e.g. "a
  1899. little"). If 'name' is not given, then all LUs will
  1900. be returned.
  1901. The valid POSes are:
  1902. v - verb
  1903. n - noun
  1904. a - adjective
  1905. adv - adverb
  1906. prep - preposition
  1907. num - numbers
  1908. intj - interjection
  1909. art - article
  1910. c - conjunction
  1911. scon - subordinating conjunction
  1912. :type name: str
  1913. :type frame: str or int or frame
  1914. :return: A list of selected (or all) lexical units
  1915. :rtype: list of LU objects (dicts). See the lu() function for info
  1916. about the specifics of LU objects.
  1917. """
  1918. if not self._lu_idx:
  1919. self._buildluindex()
  1920. if name is not None: # match LUs, then restrict by frame
  1921. result = PrettyList(
  1922. self.lu(luID) for luID, luName in self.lu_ids_and_names(name).items()
  1923. )
  1924. if frame is not None:
  1925. if isinstance(frame, int):
  1926. frameIDs = {frame}
  1927. elif isinstance(frame, str):
  1928. frameIDs = {f.ID for f in self.frames(frame)}
  1929. else:
  1930. frameIDs = {frame.ID}
  1931. result = PrettyList(lu for lu in result if lu.frame.ID in frameIDs)
  1932. elif frame is not None: # all LUs in matching frames
  1933. if isinstance(frame, int):
  1934. frames = [self.frame(frame)]
  1935. elif isinstance(frame, str):
  1936. frames = self.frames(frame)
  1937. else:
  1938. frames = [frame]
  1939. result = PrettyLazyIteratorList(
  1940. iter(LazyConcatenation(list(f.lexUnit.values()) for f in frames))
  1941. )
  1942. else: # all LUs
  1943. luIDs = [
  1944. luID
  1945. for luID, lu in self._lu_idx.items()
  1946. if lu.status not in self._bad_statuses
  1947. ]
  1948. result = PrettyLazyMap(self.lu, luIDs)
  1949. return result
  1950. def lu_ids_and_names(self, name=None):
  1951. """
  1952. Uses the LU index, which is much faster than looking up each LU definition
  1953. if only the names and IDs are needed.
  1954. """
  1955. if not self._lu_idx:
  1956. self._buildluindex()
  1957. return {
  1958. luID: luinfo.name
  1959. for luID, luinfo in self._lu_idx.items()
  1960. if luinfo.status not in self._bad_statuses
  1961. and (name is None or re.search(name, luinfo.name) is not None)
  1962. }
  1963. def docs_metadata(self, name=None):
  1964. """
  1965. Return an index of the annotated documents in Framenet.
  1966. Details for a specific annotated document can be obtained using this
  1967. class's doc() function and pass it the value of the 'ID' field.
  1968. >>> from nltk.corpus import framenet as fn
  1969. >>> len(fn.docs()) in (78, 107) # FN 1.5 and 1.7, resp.
  1970. True
  1971. >>> set([x.corpname for x in fn.docs_metadata()])>=set(['ANC', 'KBEval', \
  1972. 'LUCorpus-v0.3', 'Miscellaneous', 'NTI', 'PropBank'])
  1973. True
  1974. :param name: A regular expression pattern used to search the
  1975. file name of each annotated document. The document's
  1976. file name contains the name of the corpus that the
  1977. document is from, followed by two underscores "__"
  1978. followed by the document name. So, for example, the
  1979. file name "LUCorpus-v0.3__20000410_nyt-NEW.xml" is
  1980. from the corpus named "LUCorpus-v0.3" and the
  1981. document name is "20000410_nyt-NEW.xml".
  1982. :type name: str
  1983. :return: A list of selected (or all) annotated documents
  1984. :rtype: list of dicts, where each dict object contains the following
  1985. keys:
  1986. - 'name'
  1987. - 'ID'
  1988. - 'corpid'
  1989. - 'corpname'
  1990. - 'description'
  1991. - 'filename'
  1992. """
  1993. try:
  1994. ftlist = PrettyList(self._fulltext_idx.values())
  1995. except AttributeError:
  1996. self._buildcorpusindex()
  1997. ftlist = PrettyList(self._fulltext_idx.values())
  1998. if name is None:
  1999. return ftlist
  2000. else:
  2001. return PrettyList(
  2002. x for x in ftlist if re.search(name, x["filename"]) is not None
  2003. )
  2004. def docs(self, name=None):
  2005. """
  2006. Return a list of the annotated full-text documents in FrameNet,
  2007. optionally filtered by a regex to be matched against the document name.
  2008. """
  2009. return PrettyLazyMap((lambda x: self.doc(x.ID)), self.docs_metadata(name))
  2010. def sents(self, exemplars=True, full_text=True):
  2011. """
  2012. Annotated sentences matching the specified criteria.
  2013. """
  2014. if exemplars:
  2015. if full_text:
  2016. return self.exemplars() + self.ft_sents()
  2017. else:
  2018. return self.exemplars()
  2019. elif full_text:
  2020. return self.ft_sents()
  2021. def annotations(self, luNamePattern=None, exemplars=True, full_text=True):
  2022. """
  2023. Frame annotation sets matching the specified criteria.
  2024. """
  2025. if exemplars:
  2026. epart = PrettyLazyIteratorList(
  2027. sent.frameAnnotation for sent in self.exemplars(luNamePattern)
  2028. )
  2029. else:
  2030. epart = []
  2031. if full_text:
  2032. if luNamePattern is not None:
  2033. matchedLUIDs = set(self.lu_ids_and_names(luNamePattern).keys())
  2034. ftpart = PrettyLazyIteratorList(
  2035. aset
  2036. for sent in self.ft_sents()
  2037. for aset in sent.annotationSet[1:]
  2038. if luNamePattern is None or aset.get("luID", "CXN_ASET") in matchedLUIDs
  2039. )
  2040. else:
  2041. ftpart = []
  2042. if exemplars:
  2043. if full_text:
  2044. return epart + ftpart
  2045. else:
  2046. return epart
  2047. elif full_text:
  2048. return ftpart
  2049. def exemplars(self, luNamePattern=None, frame=None, fe=None, fe2=None):
  2050. """
  2051. Lexicographic exemplar sentences, optionally filtered by LU name and/or 1-2 FEs that
  2052. are realized overtly. 'frame' may be a name pattern, frame ID, or frame instance.
  2053. 'fe' may be a name pattern or FE instance; if specified, 'fe2' may also
  2054. be specified to retrieve sentences with both overt FEs (in either order).
  2055. """
  2056. if fe is None and fe2 is not None:
  2057. raise FramenetError("exemplars(..., fe=None, fe2=<value>) is not allowed")
  2058. elif fe is not None and fe2 is not None:
  2059. if not isinstance(fe2, str):
  2060. if isinstance(fe, str):
  2061. # fe2 is specific to a particular frame. swap fe and fe2 so fe is always used to determine the frame.
  2062. fe, fe2 = fe2, fe
  2063. elif fe.frame is not fe2.frame: # ensure frames match
  2064. raise FramenetError(
  2065. "exemplars() call with inconsistent `fe` and `fe2` specification (frames must match)"
  2066. )
  2067. if frame is None and fe is not None and not isinstance(fe, str):
  2068. frame = fe.frame
  2069. # narrow down to frames matching criteria
  2070. lusByFrame = defaultdict(
  2071. list
  2072. ) # frame name -> matching LUs, if luNamePattern is specified
  2073. if frame is not None or luNamePattern is not None:
  2074. if frame is None or isinstance(frame, str):
  2075. if luNamePattern is not None:
  2076. frames = set()
  2077. for lu in self.lus(luNamePattern, frame=frame):
  2078. frames.add(lu.frame.ID)
  2079. lusByFrame[lu.frame.name].append(lu)
  2080. frames = LazyMap(self.frame, list(frames))
  2081. else:
  2082. frames = self.frames(frame)
  2083. else:
  2084. if isinstance(frame, int):
  2085. frames = [self.frame(frame)]
  2086. else: # frame object
  2087. frames = [frame]
  2088. if luNamePattern is not None:
  2089. lusByFrame = {frame.name: self.lus(luNamePattern, frame=frame)}
  2090. if fe is not None: # narrow to frames that define this FE
  2091. if isinstance(fe, str):
  2092. frames = PrettyLazyIteratorList(
  2093. f
  2094. for f in frames
  2095. if fe in f.FE
  2096. or any(re.search(fe, ffe, re.I) for ffe in f.FE.keys())
  2097. )
  2098. else:
  2099. if fe.frame not in frames:
  2100. raise FramenetError(
  2101. "exemplars() call with inconsistent `frame` and `fe` specification"
  2102. )
  2103. frames = [fe.frame]
  2104. if fe2 is not None: # narrow to frames that ALSO define this FE
  2105. if isinstance(fe2, str):
  2106. frames = PrettyLazyIteratorList(
  2107. f
  2108. for f in frames
  2109. if fe2 in f.FE
  2110. or any(re.search(fe2, ffe, re.I) for ffe in f.FE.keys())
  2111. )
  2112. # else we already narrowed it to a single frame
  2113. else: # frame, luNamePattern are None. fe, fe2 are None or strings
  2114. if fe is not None:
  2115. frames = {ffe.frame.ID for ffe in self.fes(fe)}
  2116. if fe2 is not None:
  2117. frames2 = {ffe.frame.ID for ffe in self.fes(fe2)}
  2118. frames = frames & frames2
  2119. frames = LazyMap(self.frame, list(frames))
  2120. else:
  2121. frames = self.frames()
  2122. # we've narrowed down 'frames'
  2123. # now get exemplars for relevant LUs in those frames
  2124. def _matching_exs():
  2125. for f in frames:
  2126. fes = fes2 = None # FEs of interest
  2127. if fe is not None:
  2128. fes = (
  2129. {ffe for ffe in f.FE.keys() if re.search(fe, ffe, re.I)}
  2130. if isinstance(fe, str)
  2131. else {fe.name}
  2132. )
  2133. if fe2 is not None:
  2134. fes2 = (
  2135. {ffe for ffe in f.FE.keys() if re.search(fe2, ffe, re.I)}
  2136. if isinstance(fe2, str)
  2137. else {fe2.name}
  2138. )
  2139. for lu in (
  2140. lusByFrame[f.name]
  2141. if luNamePattern is not None
  2142. else f.lexUnit.values()
  2143. ):
  2144. for ex in lu.exemplars:
  2145. if (fes is None or self._exemplar_of_fes(ex, fes)) and (
  2146. fes2 is None or self._exemplar_of_fes(ex, fes2)
  2147. ):
  2148. yield ex
  2149. return PrettyLazyIteratorList(_matching_exs())
  2150. def _exemplar_of_fes(self, ex, fes=None):
  2151. """
  2152. Given an exemplar sentence and a set of FE names, return the subset of FE names
  2153. that are realized overtly in the sentence on the FE, FE2, or FE3 layer.
  2154. If 'fes' is None, returns all overt FE names.
  2155. """
  2156. overtNames = set(list(zip(*ex.FE[0]))[2]) if ex.FE[0] else set()
  2157. if "FE2" in ex:
  2158. overtNames |= set(list(zip(*ex.FE2[0]))[2]) if ex.FE2[0] else set()
  2159. if "FE3" in ex:
  2160. overtNames |= set(list(zip(*ex.FE3[0]))[2]) if ex.FE3[0] else set()
  2161. return overtNames & fes if fes is not None else overtNames
  2162. def ft_sents(self, docNamePattern=None):
  2163. """
  2164. Full-text annotation sentences, optionally filtered by document name.
  2165. """
  2166. return PrettyLazyIteratorList(
  2167. sent for d in self.docs(docNamePattern) for sent in d.sentence
  2168. )
  2169. def frame_relation_types(self):
  2170. """
  2171. Obtain a list of frame relation types.
  2172. >>> from nltk.corpus import framenet as fn
  2173. >>> frts = sorted(fn.frame_relation_types(), key=itemgetter('ID'))
  2174. >>> isinstance(frts, list)
  2175. True
  2176. >>> len(frts) in (9, 10) # FN 1.5 and 1.7, resp.
  2177. True
  2178. >>> PrettyDict(frts[0], breakLines=True)
  2179. {'ID': 1,
  2180. '_type': 'framerelationtype',
  2181. 'frameRelations': [<Parent=Event -- Inheritance -> Child=Change_of_consistency>, <Parent=Event -- Inheritance -> Child=Rotting>, ...],
  2182. 'name': 'Inheritance',
  2183. 'subFrameName': 'Child',
  2184. 'superFrameName': 'Parent'}
  2185. :return: A list of all of the frame relation types in framenet
  2186. :rtype: list(dict)
  2187. """
  2188. if not self._freltyp_idx:
  2189. self._buildrelationindex()
  2190. return self._freltyp_idx.values()
  2191. def frame_relations(self, frame=None, frame2=None, type=None):
  2192. """
  2193. :param frame: (optional) frame object, name, or ID; only relations involving
  2194. this frame will be returned
  2195. :param frame2: (optional; 'frame' must be a different frame) only show relations
  2196. between the two specified frames, in either direction
  2197. :param type: (optional) frame relation type (name or object); show only relations
  2198. of this type
  2199. :type frame: int or str or AttrDict
  2200. :return: A list of all of the frame relations in framenet
  2201. :rtype: list(dict)
  2202. >>> from nltk.corpus import framenet as fn
  2203. >>> frels = fn.frame_relations()
  2204. >>> isinstance(frels, list)
  2205. True
  2206. >>> len(frels) in (1676, 2070) # FN 1.5 and 1.7, resp.
  2207. True
  2208. >>> PrettyList(fn.frame_relations('Cooking_creation'), maxReprSize=0, breakLines=True)
  2209. [<Parent=Intentionally_create -- Inheritance -> Child=Cooking_creation>,
  2210. <Parent=Apply_heat -- Using -> Child=Cooking_creation>,
  2211. <MainEntry=Apply_heat -- See_also -> ReferringEntry=Cooking_creation>]
  2212. >>> PrettyList(fn.frame_relations(274), breakLines=True)
  2213. [<Parent=Avoiding -- Inheritance -> Child=Dodging>,
  2214. <Parent=Avoiding -- Inheritance -> Child=Evading>, ...]
  2215. >>> PrettyList(fn.frame_relations(fn.frame('Cooking_creation')), breakLines=True)
  2216. [<Parent=Intentionally_create -- Inheritance -> Child=Cooking_creation>,
  2217. <Parent=Apply_heat -- Using -> Child=Cooking_creation>, ...]
  2218. >>> PrettyList(fn.frame_relations('Cooking_creation', type='Inheritance'))
  2219. [<Parent=Intentionally_create -- Inheritance -> Child=Cooking_creation>]
  2220. >>> PrettyList(fn.frame_relations('Cooking_creation', 'Apply_heat'), breakLines=True)
  2221. [<Parent=Apply_heat -- Using -> Child=Cooking_creation>,
  2222. <MainEntry=Apply_heat -- See_also -> ReferringEntry=Cooking_creation>]
  2223. """
  2224. relation_type = type
  2225. if not self._frel_idx:
  2226. self._buildrelationindex()
  2227. rels = None
  2228. if relation_type is not None:
  2229. if not isinstance(relation_type, dict):
  2230. type = [rt for rt in self.frame_relation_types() if rt.name == type][0]
  2231. assert isinstance(type, dict)
  2232. # lookup by 'frame'
  2233. if frame is not None:
  2234. if isinstance(frame, dict) and "frameRelations" in frame:
  2235. rels = PrettyList(frame.frameRelations)
  2236. else:
  2237. if not isinstance(frame, int):
  2238. if isinstance(frame, dict):
  2239. frame = frame.ID
  2240. else:
  2241. frame = self.frame_by_name(frame).ID
  2242. rels = [self._frel_idx[frelID] for frelID in self._frel_f_idx[frame]]
  2243. # filter by 'type'
  2244. if type is not None:
  2245. rels = [rel for rel in rels if rel.type is type]
  2246. elif type is not None:
  2247. # lookup by 'type'
  2248. rels = type.frameRelations
  2249. else:
  2250. rels = self._frel_idx.values()
  2251. # filter by 'frame2'
  2252. if frame2 is not None:
  2253. if frame is None:
  2254. raise FramenetError(
  2255. "frame_relations(frame=None, frame2=<value>) is not allowed"
  2256. )
  2257. if not isinstance(frame2, int):
  2258. if isinstance(frame2, dict):
  2259. frame2 = frame2.ID
  2260. else:
  2261. frame2 = self.frame_by_name(frame2).ID
  2262. if frame == frame2:
  2263. raise FramenetError(
  2264. "The two frame arguments to frame_relations() must be different frames"
  2265. )
  2266. rels = [
  2267. rel
  2268. for rel in rels
  2269. if rel.superFrame.ID == frame2 or rel.subFrame.ID == frame2
  2270. ]
  2271. return PrettyList(
  2272. sorted(
  2273. rels,
  2274. key=lambda frel: (frel.type.ID, frel.superFrameName, frel.subFrameName),
  2275. )
  2276. )
  2277. def fe_relations(self):
  2278. """
  2279. Obtain a list of frame element relations.
  2280. >>> from nltk.corpus import framenet as fn
  2281. >>> ferels = fn.fe_relations()
  2282. >>> isinstance(ferels, list)
  2283. True
  2284. >>> len(ferels) in (10020, 12393) # FN 1.5 and 1.7, resp.
  2285. True
  2286. >>> PrettyDict(ferels[0], breakLines=True)
  2287. {'ID': 14642,
  2288. '_type': 'ferelation',
  2289. 'frameRelation': <Parent=Abounding_with -- Inheritance -> Child=Lively_place>,
  2290. 'subFE': <fe ID=11370 name=Degree>,
  2291. 'subFEName': 'Degree',
  2292. 'subFrame': <frame ID=1904 name=Lively_place>,
  2293. 'subID': 11370,
  2294. 'supID': 2271,
  2295. 'superFE': <fe ID=2271 name=Degree>,
  2296. 'superFEName': 'Degree',
  2297. 'superFrame': <frame ID=262 name=Abounding_with>,
  2298. 'type': <framerelationtype ID=1 name=Inheritance>}
  2299. :return: A list of all of the frame element relations in framenet
  2300. :rtype: list(dict)
  2301. """
  2302. if not self._ferel_idx:
  2303. self._buildrelationindex()
  2304. return PrettyList(
  2305. sorted(
  2306. self._ferel_idx.values(),
  2307. key=lambda ferel: (
  2308. ferel.type.ID,
  2309. ferel.frameRelation.superFrameName,
  2310. ferel.superFEName,
  2311. ferel.frameRelation.subFrameName,
  2312. ferel.subFEName,
  2313. ),
  2314. )
  2315. )
  2316. def semtypes(self):
  2317. """
  2318. Obtain a list of semantic types.
  2319. >>> from nltk.corpus import framenet as fn
  2320. >>> stypes = fn.semtypes()
  2321. >>> len(stypes) in (73, 109) # FN 1.5 and 1.7, resp.
  2322. True
  2323. >>> sorted(stypes[0].keys())
  2324. ['ID', '_type', 'abbrev', 'definition', 'definitionMarkup', 'name', 'rootType', 'subTypes', 'superType']
  2325. :return: A list of all of the semantic types in framenet
  2326. :rtype: list(dict)
  2327. """
  2328. if not self._semtypes:
  2329. self._loadsemtypes()
  2330. return PrettyList(
  2331. self._semtypes[i] for i in self._semtypes if isinstance(i, int)
  2332. )
  2333. def _load_xml_attributes(self, d, elt):
  2334. """
  2335. Extracts a subset of the attributes from the given element and
  2336. returns them in a dictionary.
  2337. :param d: A dictionary in which to store the attributes.
  2338. :type d: dict
  2339. :param elt: An ElementTree Element
  2340. :type elt: Element
  2341. :return: Returns the input dict ``d`` possibly including attributes from ``elt``
  2342. :rtype: dict
  2343. """
  2344. d = type(d)(d)
  2345. try:
  2346. attr_dict = elt.attrib
  2347. except AttributeError:
  2348. return d
  2349. if attr_dict is None:
  2350. return d
  2351. # Ignore these attributes when loading attributes from an xml node
  2352. ignore_attrs = [ #'cBy', 'cDate', 'mDate', # <-- annotation metadata that could be of interest
  2353. "xsi",
  2354. "schemaLocation",
  2355. "xmlns",
  2356. "bgColor",
  2357. "fgColor",
  2358. ]
  2359. for attr in attr_dict:
  2360. if any(attr.endswith(x) for x in ignore_attrs):
  2361. continue
  2362. val = attr_dict[attr]
  2363. if val.isdigit():
  2364. d[attr] = int(val)
  2365. else:
  2366. d[attr] = val
  2367. return d
  2368. def _strip_tags(self, data):
  2369. """
  2370. Gets rid of all tags and newline characters from the given input
  2371. :return: A cleaned-up version of the input string
  2372. :rtype: str
  2373. """
  2374. try:
  2375. """
  2376. # Look for boundary issues in markup. (Sometimes FEs are pluralized in definitions.)
  2377. m = re.search(r'\w[<][^/]|[<][/][^>]+[>](s\w|[a-rt-z0-9])', data)
  2378. if m:
  2379. print('Markup boundary:', data[max(0,m.start(0)-10):m.end(0)+10].replace('\n',' '), file=sys.stderr)
  2380. """
  2381. data = data.replace("<t>", "")
  2382. data = data.replace("</t>", "")
  2383. data = re.sub('<fex name="[^"]+">', "", data)
  2384. data = data.replace("</fex>", "")
  2385. data = data.replace("<fen>", "")
  2386. data = data.replace("</fen>", "")
  2387. data = data.replace("<m>", "")
  2388. data = data.replace("</m>", "")
  2389. data = data.replace("<ment>", "")
  2390. data = data.replace("</ment>", "")
  2391. data = data.replace("<ex>", "'")
  2392. data = data.replace("</ex>", "'")
  2393. data = data.replace("<gov>", "")
  2394. data = data.replace("</gov>", "")
  2395. data = data.replace("<x>", "")
  2396. data = data.replace("</x>", "")
  2397. # Get rid of <def-root> and </def-root> tags
  2398. data = data.replace("<def-root>", "")
  2399. data = data.replace("</def-root>", "")
  2400. data = data.replace("\n", " ")
  2401. except AttributeError:
  2402. pass
  2403. return data
  2404. def _handle_elt(self, elt, tagspec=None):
  2405. """Extracts and returns the attributes of the given element"""
  2406. return self._load_xml_attributes(AttrDict(), elt)
  2407. def _handle_fulltextindex_elt(self, elt, tagspec=None):
  2408. """
  2409. Extracts corpus/document info from the fulltextIndex.xml file.
  2410. Note that this function "flattens" the information contained
  2411. in each of the "corpus" elements, so that each "document"
  2412. element will contain attributes for the corpus and
  2413. corpusid. Also, each of the "document" items will contain a
  2414. new attribute called "filename" that is the base file name of
  2415. the xml file for the document in the "fulltext" subdir of the
  2416. Framenet corpus.
  2417. """
  2418. ftinfo = self._load_xml_attributes(AttrDict(), elt)
  2419. corpname = ftinfo.name
  2420. corpid = ftinfo.ID
  2421. retlist = []
  2422. for sub in elt:
  2423. if sub.tag.endswith("document"):
  2424. doc = self._load_xml_attributes(AttrDict(), sub)
  2425. if "name" in doc:
  2426. docname = doc.name
  2427. else:
  2428. docname = doc.description
  2429. doc.filename = "{0}__{1}.xml".format(corpname, docname)
  2430. doc.URL = (
  2431. self._fnweb_url + "/" + self._fulltext_dir + "/" + doc.filename
  2432. )
  2433. doc.corpname = corpname
  2434. doc.corpid = corpid
  2435. retlist.append(doc)
  2436. return retlist
  2437. def _handle_frame_elt(self, elt, ignorekeys=[]):
  2438. """Load the info for a Frame from a frame xml file"""
  2439. frinfo = self._load_xml_attributes(AttrDict(), elt)
  2440. frinfo["_type"] = "frame"
  2441. frinfo["definition"] = ""
  2442. frinfo["definitionMarkup"] = ""
  2443. frinfo["FE"] = PrettyDict()
  2444. frinfo["FEcoreSets"] = []
  2445. frinfo["lexUnit"] = PrettyDict()
  2446. frinfo["semTypes"] = []
  2447. for k in ignorekeys:
  2448. if k in frinfo:
  2449. del frinfo[k]
  2450. for sub in elt:
  2451. if sub.tag.endswith("definition") and "definition" not in ignorekeys:
  2452. frinfo["definitionMarkup"] = sub.text
  2453. frinfo["definition"] = self._strip_tags(sub.text)
  2454. elif sub.tag.endswith("FE") and "FE" not in ignorekeys:
  2455. feinfo = self._handle_fe_elt(sub)
  2456. frinfo["FE"][feinfo.name] = feinfo
  2457. feinfo["frame"] = frinfo # backpointer
  2458. elif sub.tag.endswith("FEcoreSet") and "FEcoreSet" not in ignorekeys:
  2459. coreset = self._handle_fecoreset_elt(sub)
  2460. # assumes all FEs have been loaded before coresets
  2461. frinfo["FEcoreSets"].append(
  2462. PrettyList(frinfo["FE"][fe.name] for fe in coreset)
  2463. )
  2464. elif sub.tag.endswith("lexUnit") and "lexUnit" not in ignorekeys:
  2465. luentry = self._handle_framelexunit_elt(sub)
  2466. if luentry["status"] in self._bad_statuses:
  2467. # problematic LU entry; ignore it
  2468. continue
  2469. luentry["frame"] = frinfo
  2470. luentry["URL"] = (
  2471. self._fnweb_url
  2472. + "/"
  2473. + self._lu_dir
  2474. + "/"
  2475. + "lu{0}.xml".format(luentry["ID"])
  2476. )
  2477. luentry["subCorpus"] = Future(
  2478. (lambda lu: lambda: self._lu_file(lu).subCorpus)(luentry)
  2479. )
  2480. luentry["exemplars"] = Future(
  2481. (lambda lu: lambda: self._lu_file(lu).exemplars)(luentry)
  2482. )
  2483. frinfo["lexUnit"][luentry.name] = luentry
  2484. if not self._lu_idx:
  2485. self._buildluindex()
  2486. self._lu_idx[luentry.ID] = luentry
  2487. elif sub.tag.endswith("semType") and "semTypes" not in ignorekeys:
  2488. semtypeinfo = self._load_xml_attributes(AttrDict(), sub)
  2489. frinfo["semTypes"].append(self.semtype(semtypeinfo.ID))
  2490. frinfo["frameRelations"] = self.frame_relations(frame=frinfo)
  2491. # resolve 'requires' and 'excludes' links between FEs of this frame
  2492. for fe in frinfo.FE.values():
  2493. if fe.requiresFE:
  2494. name, ID = fe.requiresFE.name, fe.requiresFE.ID
  2495. fe.requiresFE = frinfo.FE[name]
  2496. assert fe.requiresFE.ID == ID
  2497. if fe.excludesFE:
  2498. name, ID = fe.excludesFE.name, fe.excludesFE.ID
  2499. fe.excludesFE = frinfo.FE[name]
  2500. assert fe.excludesFE.ID == ID
  2501. return frinfo
  2502. def _handle_fecoreset_elt(self, elt):
  2503. """Load fe coreset info from xml."""
  2504. info = self._load_xml_attributes(AttrDict(), elt)
  2505. tmp = []
  2506. for sub in elt:
  2507. tmp.append(self._load_xml_attributes(AttrDict(), sub))
  2508. return tmp
  2509. def _handle_framerelationtype_elt(self, elt, *args):
  2510. """Load frame-relation element and its child fe-relation elements from frRelation.xml."""
  2511. info = self._load_xml_attributes(AttrDict(), elt)
  2512. info["_type"] = "framerelationtype"
  2513. info["frameRelations"] = PrettyList()
  2514. for sub in elt:
  2515. if sub.tag.endswith("frameRelation"):
  2516. frel = self._handle_framerelation_elt(sub)
  2517. frel["type"] = info # backpointer
  2518. for ferel in frel.feRelations:
  2519. ferel["type"] = info
  2520. info["frameRelations"].append(frel)
  2521. return info
  2522. def _handle_framerelation_elt(self, elt):
  2523. """Load frame-relation element and its child fe-relation elements from frRelation.xml."""
  2524. info = self._load_xml_attributes(AttrDict(), elt)
  2525. assert info["superFrameName"] != info["subFrameName"], (elt, info)
  2526. info["_type"] = "framerelation"
  2527. info["feRelations"] = PrettyList()
  2528. for sub in elt:
  2529. if sub.tag.endswith("FERelation"):
  2530. ferel = self._handle_elt(sub)
  2531. ferel["_type"] = "ferelation"
  2532. ferel["frameRelation"] = info # backpointer
  2533. info["feRelations"].append(ferel)
  2534. return info
  2535. def _handle_fulltextannotation_elt(self, elt):
  2536. """Load full annotation info for a document from its xml
  2537. file. The main element (fullTextAnnotation) contains a 'header'
  2538. element (which we ignore here) and a bunch of 'sentence'
  2539. elements."""
  2540. info = AttrDict()
  2541. info["_type"] = "fulltext_annotation"
  2542. info["sentence"] = []
  2543. for sub in elt:
  2544. if sub.tag.endswith("header"):
  2545. continue # not used
  2546. elif sub.tag.endswith("sentence"):
  2547. s = self._handle_fulltext_sentence_elt(sub)
  2548. s.doc = info
  2549. info["sentence"].append(s)
  2550. return info
  2551. def _handle_fulltext_sentence_elt(self, elt):
  2552. """Load information from the given 'sentence' element. Each
  2553. 'sentence' element contains a "text" and "annotationSet" sub
  2554. elements."""
  2555. info = self._load_xml_attributes(AttrDict(), elt)
  2556. info["_type"] = "fulltext_sentence"
  2557. info["annotationSet"] = []
  2558. info["targets"] = []
  2559. target_spans = set()
  2560. info["_ascii"] = types.MethodType(
  2561. _annotation_ascii, info
  2562. ) # attach a method for this instance
  2563. info["text"] = ""
  2564. for sub in elt:
  2565. if sub.tag.endswith("text"):
  2566. info["text"] = self._strip_tags(sub.text)
  2567. elif sub.tag.endswith("annotationSet"):
  2568. a = self._handle_fulltextannotationset_elt(
  2569. sub, is_pos=(len(info["annotationSet"]) == 0)
  2570. )
  2571. if "cxnID" in a: # ignoring construction annotations for now
  2572. continue
  2573. a.sent = info
  2574. a.text = info.text
  2575. info["annotationSet"].append(a)
  2576. if "Target" in a:
  2577. for tspan in a.Target:
  2578. if tspan in target_spans:
  2579. self._warn(
  2580. 'Duplicate target span "{0}"'.format(
  2581. info.text[slice(*tspan)]
  2582. ),
  2583. tspan,
  2584. "in sentence",
  2585. info["ID"],
  2586. info.text,
  2587. )
  2588. # this can happen in cases like "chemical and biological weapons"
  2589. # being annotated as "chemical weapons" and "biological weapons"
  2590. else:
  2591. target_spans.add(tspan)
  2592. info["targets"].append((a.Target, a.luName, a.frameName))
  2593. assert info["annotationSet"][0].status == "UNANN"
  2594. info["POS"] = info["annotationSet"][0].POS
  2595. info["POS_tagset"] = info["annotationSet"][0].POS_tagset
  2596. return info
  2597. def _handle_fulltextannotationset_elt(self, elt, is_pos=False):
  2598. """Load information from the given 'annotationSet' element. Each
  2599. 'annotationSet' contains several "layer" elements."""
  2600. info = self._handle_luannotationset_elt(elt, is_pos=is_pos)
  2601. if not is_pos:
  2602. info["_type"] = "fulltext_annotationset"
  2603. if "cxnID" not in info: # ignoring construction annotations for now
  2604. info["LU"] = self.lu(
  2605. info.luID,
  2606. luName=info.luName,
  2607. frameID=info.frameID,
  2608. frameName=info.frameName,
  2609. )
  2610. info["frame"] = info.LU.frame
  2611. return info
  2612. def _handle_fulltextlayer_elt(self, elt):
  2613. """Load information from the given 'layer' element. Each
  2614. 'layer' contains several "label" elements."""
  2615. info = self._load_xml_attributes(AttrDict(), elt)
  2616. info["_type"] = "layer"
  2617. info["label"] = []
  2618. for sub in elt:
  2619. if sub.tag.endswith("label"):
  2620. l = self._load_xml_attributes(AttrDict(), sub)
  2621. info["label"].append(l)
  2622. return info
  2623. def _handle_framelexunit_elt(self, elt):
  2624. """Load the lexical unit info from an xml element in a frame's xml file."""
  2625. luinfo = AttrDict()
  2626. luinfo["_type"] = "lu"
  2627. luinfo = self._load_xml_attributes(luinfo, elt)
  2628. luinfo["definition"] = ""
  2629. luinfo["definitionMarkup"] = ""
  2630. luinfo["sentenceCount"] = PrettyDict()
  2631. luinfo["lexemes"] = PrettyList() # multiword LUs have multiple lexemes
  2632. luinfo["semTypes"] = PrettyList() # an LU can have multiple semtypes
  2633. for sub in elt:
  2634. if sub.tag.endswith("definition"):
  2635. luinfo["definitionMarkup"] = sub.text
  2636. luinfo["definition"] = self._strip_tags(sub.text)
  2637. elif sub.tag.endswith("sentenceCount"):
  2638. luinfo["sentenceCount"] = self._load_xml_attributes(PrettyDict(), sub)
  2639. elif sub.tag.endswith("lexeme"):
  2640. lexemeinfo = self._load_xml_attributes(PrettyDict(), sub)
  2641. if not isinstance(lexemeinfo.name, str):
  2642. # some lexeme names are ints by default: e.g.,
  2643. # thousand.num has lexeme with name="1000"
  2644. lexemeinfo.name = str(lexemeinfo.name)
  2645. luinfo["lexemes"].append(lexemeinfo)
  2646. elif sub.tag.endswith("semType"):
  2647. semtypeinfo = self._load_xml_attributes(PrettyDict(), sub)
  2648. luinfo["semTypes"].append(self.semtype(semtypeinfo.ID))
  2649. # sort lexemes by 'order' attribute
  2650. # otherwise, e.g., 'write down.v' may have lexemes in wrong order
  2651. luinfo["lexemes"].sort(key=lambda x: x.order)
  2652. return luinfo
  2653. def _handle_lexunit_elt(self, elt, ignorekeys):
  2654. """
  2655. Load full info for a lexical unit from its xml file.
  2656. This should only be called when accessing corpus annotations
  2657. (which are not included in frame files).
  2658. """
  2659. luinfo = self._load_xml_attributes(AttrDict(), elt)
  2660. luinfo["_type"] = "lu"
  2661. luinfo["definition"] = ""
  2662. luinfo["definitionMarkup"] = ""
  2663. luinfo["subCorpus"] = PrettyList()
  2664. luinfo["lexemes"] = PrettyList() # multiword LUs have multiple lexemes
  2665. luinfo["semTypes"] = PrettyList() # an LU can have multiple semtypes
  2666. for k in ignorekeys:
  2667. if k in luinfo:
  2668. del luinfo[k]
  2669. for sub in elt:
  2670. if sub.tag.endswith("header"):
  2671. continue # not used
  2672. elif sub.tag.endswith("valences"):
  2673. continue # not used
  2674. elif sub.tag.endswith("definition") and "definition" not in ignorekeys:
  2675. luinfo["definitionMarkup"] = sub.text
  2676. luinfo["definition"] = self._strip_tags(sub.text)
  2677. elif sub.tag.endswith("subCorpus") and "subCorpus" not in ignorekeys:
  2678. sc = self._handle_lusubcorpus_elt(sub)
  2679. if sc is not None:
  2680. luinfo["subCorpus"].append(sc)
  2681. elif sub.tag.endswith("lexeme") and "lexeme" not in ignorekeys:
  2682. luinfo["lexemes"].append(self._load_xml_attributes(PrettyDict(), sub))
  2683. elif sub.tag.endswith("semType") and "semType" not in ignorekeys:
  2684. semtypeinfo = self._load_xml_attributes(AttrDict(), sub)
  2685. luinfo["semTypes"].append(self.semtype(semtypeinfo.ID))
  2686. return luinfo
  2687. def _handle_lusubcorpus_elt(self, elt):
  2688. """Load a subcorpus of a lexical unit from the given xml."""
  2689. sc = AttrDict()
  2690. try:
  2691. sc["name"] = elt.get("name")
  2692. except AttributeError:
  2693. return None
  2694. sc["_type"] = "lusubcorpus"
  2695. sc["sentence"] = []
  2696. for sub in elt:
  2697. if sub.tag.endswith("sentence"):
  2698. s = self._handle_lusentence_elt(sub)
  2699. if s is not None:
  2700. sc["sentence"].append(s)
  2701. return sc
  2702. def _handle_lusentence_elt(self, elt):
  2703. """Load a sentence from a subcorpus of an LU from xml."""
  2704. info = self._load_xml_attributes(AttrDict(), elt)
  2705. info["_type"] = "lusentence"
  2706. info["annotationSet"] = []
  2707. info["_ascii"] = types.MethodType(
  2708. _annotation_ascii, info
  2709. ) # attach a method for this instance
  2710. for sub in elt:
  2711. if sub.tag.endswith("text"):
  2712. info["text"] = self._strip_tags(sub.text)
  2713. elif sub.tag.endswith("annotationSet"):
  2714. annset = self._handle_luannotationset_elt(
  2715. sub, is_pos=(len(info["annotationSet"]) == 0)
  2716. )
  2717. if annset is not None:
  2718. assert annset.status == "UNANN" or "FE" in annset, annset
  2719. if annset.status != "UNANN":
  2720. info["frameAnnotation"] = annset
  2721. # copy layer info up to current level
  2722. for k in (
  2723. "Target",
  2724. "FE",
  2725. "FE2",
  2726. "FE3",
  2727. "GF",
  2728. "PT",
  2729. "POS",
  2730. "POS_tagset",
  2731. "Other",
  2732. "Sent",
  2733. "Verb",
  2734. "Noun",
  2735. "Adj",
  2736. "Adv",
  2737. "Prep",
  2738. "Scon",
  2739. "Art",
  2740. ):
  2741. if k in annset:
  2742. info[k] = annset[k]
  2743. info["annotationSet"].append(annset)
  2744. annset["sent"] = info
  2745. annset["text"] = info.text
  2746. return info
  2747. def _handle_luannotationset_elt(self, elt, is_pos=False):
  2748. """Load an annotation set from a sentence in an subcorpus of an LU"""
  2749. info = self._load_xml_attributes(AttrDict(), elt)
  2750. info["_type"] = "posannotationset" if is_pos else "luannotationset"
  2751. info["layer"] = []
  2752. info["_ascii"] = types.MethodType(
  2753. _annotation_ascii, info
  2754. ) # attach a method for this instance
  2755. if "cxnID" in info: # ignoring construction annotations for now.
  2756. return info
  2757. for sub in elt:
  2758. if sub.tag.endswith("layer"):
  2759. l = self._handle_lulayer_elt(sub)
  2760. if l is not None:
  2761. overt = []
  2762. ni = {} # null instantiations
  2763. info["layer"].append(l)
  2764. for lbl in l.label:
  2765. if "start" in lbl:
  2766. thespan = (lbl.start, lbl.end + 1, lbl.name)
  2767. if l.name not in (
  2768. "Sent",
  2769. "Other",
  2770. ): # 'Sent' and 'Other' layers sometimes contain accidental duplicate spans
  2771. assert thespan not in overt, (info.ID, l.name, thespan)
  2772. overt.append(thespan)
  2773. else: # null instantiation
  2774. if lbl.name in ni:
  2775. self._warn(
  2776. "FE with multiple NI entries:",
  2777. lbl.name,
  2778. ni[lbl.name],
  2779. lbl.itype,
  2780. )
  2781. else:
  2782. ni[lbl.name] = lbl.itype
  2783. overt = sorted(overt)
  2784. if l.name == "Target":
  2785. if not overt:
  2786. self._warn(
  2787. "Skipping empty Target layer in annotation set ID={0}".format(
  2788. info.ID
  2789. )
  2790. )
  2791. continue
  2792. assert all(lblname == "Target" for i, j, lblname in overt)
  2793. if "Target" in info:
  2794. self._warn(
  2795. "Annotation set {0} has multiple Target layers".format(
  2796. info.ID
  2797. )
  2798. )
  2799. else:
  2800. info["Target"] = [(i, j) for (i, j, _) in overt]
  2801. elif l.name == "FE":
  2802. if l.rank == 1:
  2803. assert "FE" not in info
  2804. info["FE"] = (overt, ni)
  2805. # assert False,info
  2806. else:
  2807. # sometimes there are 3 FE layers! e.g. Change_position_on_a_scale.fall.v
  2808. assert 2 <= l.rank <= 3, l.rank
  2809. k = "FE" + str(l.rank)
  2810. assert k not in info
  2811. info[k] = (overt, ni)
  2812. elif l.name in ("GF", "PT"):
  2813. assert l.rank == 1
  2814. info[l.name] = overt
  2815. elif l.name in ("BNC", "PENN"):
  2816. assert l.rank == 1
  2817. info["POS"] = overt
  2818. info["POS_tagset"] = l.name
  2819. else:
  2820. if is_pos:
  2821. if l.name not in ("NER", "WSL"):
  2822. self._warn(
  2823. "Unexpected layer in sentence annotationset:",
  2824. l.name,
  2825. )
  2826. else:
  2827. if l.name not in (
  2828. "Sent",
  2829. "Verb",
  2830. "Noun",
  2831. "Adj",
  2832. "Adv",
  2833. "Prep",
  2834. "Scon",
  2835. "Art",
  2836. "Other",
  2837. ):
  2838. self._warn(
  2839. "Unexpected layer in frame annotationset:", l.name
  2840. )
  2841. info[l.name] = overt
  2842. if not is_pos and "cxnID" not in info:
  2843. if "Target" not in info:
  2844. self._warn("Missing target in annotation set ID={0}".format(info.ID))
  2845. assert "FE" in info
  2846. if "FE3" in info:
  2847. assert "FE2" in info
  2848. return info
  2849. def _handle_lulayer_elt(self, elt):
  2850. """Load a layer from an annotation set"""
  2851. layer = self._load_xml_attributes(AttrDict(), elt)
  2852. layer["_type"] = "lulayer"
  2853. layer["label"] = []
  2854. for sub in elt:
  2855. if sub.tag.endswith("label"):
  2856. l = self._load_xml_attributes(AttrDict(), sub)
  2857. if l is not None:
  2858. layer["label"].append(l)
  2859. return layer
  2860. def _handle_fe_elt(self, elt):
  2861. feinfo = self._load_xml_attributes(AttrDict(), elt)
  2862. feinfo["_type"] = "fe"
  2863. feinfo["definition"] = ""
  2864. feinfo["definitionMarkup"] = ""
  2865. feinfo["semType"] = None
  2866. feinfo["requiresFE"] = None
  2867. feinfo["excludesFE"] = None
  2868. for sub in elt:
  2869. if sub.tag.endswith("definition"):
  2870. feinfo["definitionMarkup"] = sub.text
  2871. feinfo["definition"] = self._strip_tags(sub.text)
  2872. elif sub.tag.endswith("semType"):
  2873. stinfo = self._load_xml_attributes(AttrDict(), sub)
  2874. feinfo["semType"] = self.semtype(stinfo.ID)
  2875. elif sub.tag.endswith("requiresFE"):
  2876. feinfo["requiresFE"] = self._load_xml_attributes(AttrDict(), sub)
  2877. elif sub.tag.endswith("excludesFE"):
  2878. feinfo["excludesFE"] = self._load_xml_attributes(AttrDict(), sub)
  2879. return feinfo
  2880. def _handle_semtype_elt(self, elt, tagspec=None):
  2881. semt = self._load_xml_attributes(AttrDict(), elt)
  2882. semt["_type"] = "semtype"
  2883. semt["superType"] = None
  2884. semt["subTypes"] = PrettyList()
  2885. for sub in elt:
  2886. if sub.text is not None:
  2887. semt["definitionMarkup"] = sub.text
  2888. semt["definition"] = self._strip_tags(sub.text)
  2889. else:
  2890. supertypeinfo = self._load_xml_attributes(AttrDict(), sub)
  2891. semt["superType"] = supertypeinfo
  2892. # the supertype may not have been loaded yet
  2893. return semt
  2894. #
  2895. # Demo
  2896. #
  2897. def demo():
  2898. from nltk.corpus import framenet as fn
  2899. #
  2900. # It is not necessary to explicitly build the indexes by calling
  2901. # buildindexes(). We do this here just for demo purposes. If the
  2902. # indexes are not built explicitely, they will be built as needed.
  2903. #
  2904. print("Building the indexes...")
  2905. fn.buildindexes()
  2906. #
  2907. # Get some statistics about the corpus
  2908. #
  2909. print("Number of Frames:", len(fn.frames()))
  2910. print("Number of Lexical Units:", len(fn.lus()))
  2911. print("Number of annotated documents:", len(fn.docs()))
  2912. print()
  2913. #
  2914. # Frames
  2915. #
  2916. print(
  2917. 'getting frames whose name matches the (case insensitive) regex: "(?i)medical"'
  2918. )
  2919. medframes = fn.frames(r"(?i)medical")
  2920. print('Found {0} Frames whose name matches "(?i)medical":'.format(len(medframes)))
  2921. print([(f.name, f.ID) for f in medframes])
  2922. #
  2923. # store the first frame in the list of frames
  2924. #
  2925. tmp_id = medframes[0].ID
  2926. m_frame = fn.frame(tmp_id) # reads all info for the frame
  2927. #
  2928. # get the frame relations
  2929. #
  2930. print(
  2931. '\nNumber of frame relations for the "{0}" ({1}) frame:'.format(
  2932. m_frame.name, m_frame.ID
  2933. ),
  2934. len(m_frame.frameRelations),
  2935. )
  2936. for fr in m_frame.frameRelations:
  2937. print(" ", fr)
  2938. #
  2939. # get the names of the Frame Elements
  2940. #
  2941. print(
  2942. '\nNumber of Frame Elements in the "{0}" frame:'.format(m_frame.name),
  2943. len(m_frame.FE),
  2944. )
  2945. print(" ", [x for x in m_frame.FE])
  2946. #
  2947. # get the names of the "Core" Frame Elements
  2948. #
  2949. print('\nThe "core" Frame Elements in the "{0}" frame:'.format(m_frame.name))
  2950. print(" ", [x.name for x in m_frame.FE.values() if x.coreType == "Core"])
  2951. #
  2952. # get all of the Lexical Units that are incorporated in the
  2953. # 'Ailment' FE of the 'Medical_conditions' frame (id=239)
  2954. #
  2955. print('\nAll Lexical Units that are incorporated in the "Ailment" FE:')
  2956. m_frame = fn.frame(239)
  2957. ailment_lus = [
  2958. x
  2959. for x in m_frame.lexUnit.values()
  2960. if "incorporatedFE" in x and x.incorporatedFE == "Ailment"
  2961. ]
  2962. print(" ", [x.name for x in ailment_lus])
  2963. #
  2964. # get all of the Lexical Units for the frame
  2965. #
  2966. print(
  2967. '\nNumber of Lexical Units in the "{0}" frame:'.format(m_frame.name),
  2968. len(m_frame.lexUnit),
  2969. )
  2970. print(" ", [x.name for x in m_frame.lexUnit.values()][:5], "...")
  2971. #
  2972. # get basic info on the second LU in the frame
  2973. #
  2974. tmp_id = m_frame.lexUnit["ailment.n"].ID # grab the id of the specified LU
  2975. luinfo = fn.lu_basic(tmp_id) # get basic info on the LU
  2976. print("\nInformation on the LU: {0}".format(luinfo.name))
  2977. pprint(luinfo)
  2978. #
  2979. # Get a list of all of the corpora used for fulltext annotation
  2980. #
  2981. print("\nNames of all of the corpora used for fulltext annotation:")
  2982. allcorpora = set(x.corpname for x in fn.docs_metadata())
  2983. pprint(list(allcorpora))
  2984. #
  2985. # Get the names of the annotated documents in the first corpus
  2986. #
  2987. firstcorp = list(allcorpora)[0]
  2988. firstcorp_docs = fn.docs(firstcorp)
  2989. print('\nNames of the annotated documents in the "{0}" corpus:'.format(firstcorp))
  2990. pprint([x.filename for x in firstcorp_docs])
  2991. #
  2992. # Search for frames containing LUs whose name attribute matches a
  2993. # regexp pattern.
  2994. #
  2995. # Note: if you were going to be doing a lot of this type of
  2996. # searching, you'd want to build an index that maps from
  2997. # lemmas to frames because each time frames_by_lemma() is
  2998. # called, it has to search through ALL of the frame XML files
  2999. # in the db.
  3000. print(
  3001. '\nSearching for all Frames that have a lemma that matches the regexp: "^run.v$":'
  3002. )
  3003. pprint(fn.frames_by_lemma(r"^run.v$"))
  3004. if __name__ == "__main__":
  3005. demo()