#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import absolute_import
import collections
import six
import re
import sys
[docs]class Argument(object):
""" 項に関する情報を保持するオブジェクト
詳しくは下記ページの「格要素側」の記述方法を参照
http://nlp.ist.i.kyoto-u.ac.jp/index.php?KNP%2F%E6%A0%BC%E8%A7%A3%E6%9E%90%E7%B5%90%E6%9E%9C%E6%9B%B8%E5%BC%8F
Attributes:
sid (str): 文ID
tid (int): 基本句ID
eid (int): Entity ID
midasi (str): 表記
flag (str): フラグ (C, N, O, D, E, U)
sdist (int): 述語の何文前か
"""
[docs] def __init__(self, sid=None, tid=None, eid=None, midasi='', flag=None, sdist=None):
assert isinstance(tid, int)
assert isinstance(midasi, six.text_type)
self.sid = sid
self.tid = tid
self.eid = eid
self.midasi = midasi
self.flag = flag
self.sdist = sdist
ArgRepname = collections.namedtuple("ArgRepname", "repname,tid_list")
class CaseInfoFormat(object):
""" 格/述語項構造のフォーマットを管理する名前空間 """
CASE = 0 # 格解析フォーマット
PASv41 = 1 # KNP v4.1 での述語項構造フォーマット
PASv42 = 2 # KNP v4.2 での述語項構造フォーマット
[docs]class Pas(object):
""" 述語項構造を扱うクラス
Usage:
result = knp.result(knp_result)
pas = result.tag_list()[tid].pas # tid番目の基本句(述語)の項構造
Attributes:
cfid (str): 格フレームID (例: "行う/おこなう:動10")
arguments (dict of (case, list of Argument)):
格と項を対応付けた辞書 {case: [Argument, ..]}
keyは格を表す文字列, valueはArgumentオブジェクトのリスト。
リスト形式なのは、ガ格などは複数の項を取り得るため。
"""
[docs] def __init__(self, tid=None, result=None):
self.valid = True
self.cfid = None
self.arguments = collections.defaultdict(list)
if tid is None:
self.valid = False
return
self.tid = tid
self.tag_list = result.tag_list()
self.tid2sdist = {}
self.sid = result.sid
tag_predicate = self.tag_list[self.tid]
if "項構造" in tag_predicate.features: # KNP v4.1 で -anaphora
# (eid, tid, sdist) の対を記録し、dictに保持(eid2tid, tid2sdist)
# eid2tidはeidのエンティティが初出のTag位置(tid)を保持
self.eid2tid = {}
for tag in self.tag_list:
if 'EID' in tag.features:
eid = int(tag.features['EID'])
self.eid2tid[eid] = tag.tag_id
# (tid, sdist) を記録するため、"格解析結果"の値をパース
case_analysis = tag_predicate.features.get("格解析結果")
for items in self.__parse_case_analysis_items(case_analysis, CaseInfoFormat.CASE):
(mycase, caseflag, midasi, eid, tid, sdist, sid) = items
self.tid2sdist[tid] = sdist
self.__set_args(tag_predicate.features.get("項構造"), CaseInfoFormat.PASv41)
elif "述語項構造" in tag_predicate.features: # KNP v4.2 で -anaphora
self.__set_args(tag_predicate.features.get("述語項構造"), CaseInfoFormat.PASv42)
elif "格解析結果" in tag_predicate.features:
self.__set_args(tag_predicate.features.get("格解析結果"), CaseInfoFormat.CASE)
else:
self.valid = False
[docs] def is_valid(self):
return self.valid
[docs] def get_arguments(self, case):
"""
指定した格の各項ごとに代表表記の配列を返す
Usage:
result = knp.parse("研究者になる")
print(result.tag_list()[2].midasi)
>> なる
print(result.tag_list()[2].pas.get_arguments("ニ"))
>> [ArgRepname(repname='者/しゃ', tid_list=1)]
Args:
case (str): 格の文字列
Returns:
list: 項の代表表記を格納したnamedtupleである ArgRepname のリスト
"""
output = []
for arg in self.arguments[case]:
tid = arg.tid
if self.tag_list[tid].head_prime_repname:
rep = self.tag_list[tid].head_prime_repname
else:
rep = self.tag_list[tid].repname
output.append(ArgRepname(rep, tid))
return output
[docs] def get_orig_result(self):
return self.tag_list[self.tid].features.get("格解析結果")
def __parse_case_info_format(self, items, case_info_format):
""" 格/述語項構造フォーマットを読み取る """
if case_info_format == CaseInfoFormat.CASE:
mycase = items[0]
caseflag = items[1]
midasi = items[2]
tid = int(items[3])
sdist = int(items[4])
sid = items[5]
eid = None
elif case_info_format == CaseInfoFormat.PASv41:
mycase = items[0]
caseflag = items[1]
midasi = items[2]
eid = int(items[3])
if eid in self.eid2tid: # eidのエンティティが同一文内にない場合、KeyError
tid = self.eid2tid[eid]
sdist = self.tid2sdist[tid] if tid in self.tid2sdist else None
sid = self.sid
else:
tid = -1 # FIXME: EIDが登録された文内のTag位置
sdist = None
sid = "-1" # FIXME: EIDが登録された文のsid (複数文を辿れる必要がある)
else: # FIXME: tid,sidについてv4.1と同じ問題がある
assert case_info_format == CaseInfoFormat.PASv42
mycase = items[0]
caseflag = items[1]
midasi = items[2]
sdist = int(items[3])
tid = int(items[4])
eid = int(items[5])
sid = self.sid
return mycase, caseflag, midasi, eid, tid, sdist, sid
def __parse_case_analysis_items(self, analysis_result, case_info_format):
""" 述語情報の設定・格情報の抽出 """
assert isinstance(analysis_result, six.text_type)
# language=RegExp
cfid_pat = r'(.*?):([^:/]+?)'
if case_info_format == CaseInfoFormat.CASE:
# language=RegExp
arg_pat = r'(.+?/[CNODEU-]/.+?(?:/(?:-|\d+)){2}/[^;/]+)'
elif case_info_format == CaseInfoFormat.PASv41:
# language=RegExp
arg_pat = r'(.+?/[CNODEU-]/.+?/(?:-|\d+))'
else:
assert case_info_format == CaseInfoFormat.PASv42
# language=RegExp
arg_pat = r'(.+?/[CNODEU-]/.+?(?:/(?:-?\d*)){3})'
match = re.match(r'{}(?::{}|$)'.format(cfid_pat, arg_pat), analysis_result)
if match is None:
print("invalid tag format: '{}' is ignored".format(analysis_result), file=sys.stderr)
return
self.cfid = match.group(1) + ':' + match.group(2)
if match.group(3) is None: # <述語項構造:束の間/つかのま:判0> など
return
arg_pat_compiled = re.compile(';' + arg_pat)
cases = [match.group(3)]
pos = match.end(3)
while True:
match = arg_pat_compiled.match(analysis_result, pos=pos)
if match is None:
break
cases.append(match.group(1))
pos = match.end(1)
for k in cases:
items = k.split('/')
caseflag = items[1]
if caseflag == 'U' or caseflag == '-':
continue
yield self.__parse_case_info_format(items, case_info_format)
def __set_args(self, analysis_result, case_info_format):
""" 述語項構造情報をself.argumentsに設定 """
for items in self.__parse_case_analysis_items(analysis_result, case_info_format):
(mycase, caseflag, midasi, eid, tid, sdist, sid) = items
arg = Argument(sid=sid, tid=tid, eid=eid, midasi=midasi, flag=caseflag, sdist=sdist)
self.arguments[mycase].append(arg)