误匹配治理:裸年份改白名单、公元数字加边界、年数上界与民国窗口校验
This commit is contained in:
@@ -317,19 +317,19 @@ _known_era_alt = '|'.join(sorted(KNOWN_ERAS_ALL, key=len, reverse=True))
|
||||
_known_era_group = r'(?P<era>' + _known_era_alt + r')'
|
||||
_yd = '[' + _year_digits + ']'
|
||||
# 年份后缀:"年"字可省(碑谱/契约常见:"民国二十六""万历三十六",也支持后接干支
|
||||
# "光绪二十六庚子")。省"年"时靠负向前瞻 _year_guard 兜住误匹配:
|
||||
# 数字后面若还是数字字符或 万/亿,说明它只是一个更长数量的一部分("万历二十万"),不认。
|
||||
# 该 guard 同时挡住正则回溯:若整体匹配失败而回退到更短的数字串,其下一个字符必然仍是
|
||||
# 数字字符,guard 同样不通过(Python 3.10 没有原子组/占有量词,只能这样写)。
|
||||
# 裸年份(后面既无"年"、也无干支)后面若跟这些字,说明数字不是年数,一律不认:
|
||||
# 月/日/号 → 月份日期("康熙六月""民国七月十二")
|
||||
# 子/女/弟/兄/孙/婿/郎 → 亲属排行("张景福三子")
|
||||
# 都/图/里 → 地名/行政区("永明五十二都")
|
||||
# 岁/人/名/位/个/两/斤/石/亩/间/匹/只/件/张/条/次/层/种/倍/余/多 → 量词/约数
|
||||
# 注意不含天干(甲乙丙丁…):紧跟干支的写法("光绪二十六庚子")不受影响。
|
||||
_bare_year_forbid = '月日号子女子弟兄孙婿郎都图里岁人名位个两次条张匹只件斤两石亩间层种倍余多'
|
||||
_year_guard = r'(?![' + _year_digits + r'万亿' + _bare_year_forbid + r'])'
|
||||
_year_suffix = _year_guard + r'(?:年)?'
|
||||
# "光绪二十六庚子")。省"年"时用**白名单前瞻**兜住误匹配——数字后只有跟下列内容才算年份:
|
||||
# ① 族谱常用动词:生卒殁殇死终葬娶适嫁亡逝享寿嗣立
|
||||
# ② 标点/括号/引号/空白/行尾:,。、;:,.;:!?!?""''()()[]【】{}《》<> 及 \s、$
|
||||
# ③ 天干(甲乙丙丁戊己庚辛壬癸):后面就是干支,由各段自行并入并校验
|
||||
# 于是这些都不再当年份:康熙六月(月)、万历三大征(大)、康熙四十二卷(卷/42卷)、
|
||||
# 张景福三子(子/排行)、永明五十二都(都/地名)、万历二十万(万)、光绪二十六日(日)、
|
||||
# 康熙二十天(天/20天)。白名单也天然挡住正则回溯:回退到更短数字串时其后仍是数字,不在表内。
|
||||
_bare_year_after = (
|
||||
'生卒殁殤殇死终終葬娶适適嫁亡逝享寿壽嗣立'
|
||||
+ ',。、;:,.;:!?!?“”"\'‘’()()[]【】{}《》<>'
|
||||
+ _tiangan
|
||||
)
|
||||
_year_suffix = r'(?:年|(?=[' + re.escape(_bare_year_after) + r'\s]|$))'
|
||||
|
||||
def _build_pattern(with_dynasty=True):
|
||||
"""构建年号正则:已知年号优先 + 通用兜底(仅限有朝代前缀时)"""
|
||||
@@ -377,8 +377,12 @@ _patterns_dynasty = _build_pattern(with_dynasty=True)
|
||||
# 置于公元段之前(见 extract_era_years),避免对照年份被单独摘出成公元纪年。
|
||||
_dynasty_opt = (r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))'
|
||||
r'|(?:(?<![一-鿿])' + _dynasty_alt + r'))?')
|
||||
# 公元对照年份:阿拉伯/全角/中文数字 3-4 位,可带"年"字
|
||||
_gz_gy_year = r'[〇零○O一二三四五六七八九\d0-9]{3,4}'
|
||||
# 公元对照年份:阿拉伯/全角/中文数字 3-4 位,可带"年"字。
|
||||
# 两端加数字边界:防止从更长数字串里截出 3-4 位当公元(数据里的标注 ID "{SPZ146356}"
|
||||
# 会截出 1463、电话 13800138000 会截出 1380、编号 1987654 会截出 1987)。
|
||||
_gy_edge = '0-90-9〇零一二三四五六七八九'
|
||||
_gz_gy_year = (r'(?<![' + _gy_edge + r'])' + r'[〇零○O一二三四五六七八九\d0-9]{3,4}'
|
||||
+ r'(?![' + _gy_edge + r'])')
|
||||
# 干支与对照年份之间的连接方式(按优先级):
|
||||
# 括号(1872)/ 公元标记(,公元1872年)/ 横线(-1872)/ 无分隔连写(壬申1872年)
|
||||
# 空白只允许空格/制表符([ \t]*),不允许换行——行尾的 \n 不得被吞进原文
|
||||
@@ -447,6 +451,11 @@ def extract_era_years(text):
|
||||
break
|
||||
if gy_year is not None and not (1000 <= gy_year <= 2100):
|
||||
continue
|
||||
gregorian = gy_year if gy_year is not None else 1911 + year_num
|
||||
# 年数上界:不存在"民国2000年"(→3911)、"民国146356"(→148267)这类写法,
|
||||
# 换算出的公元超出支持窗口(1000–2100)直接不认——与公元段同一窗口约定
|
||||
if not (1000 <= gregorian <= 2100):
|
||||
continue
|
||||
start, end = m.start(), m.end()
|
||||
# 尾随干支校验(如"民国三十八年己丑"):并入原文同一单位(同公元段逻辑)
|
||||
ganzhi = simplified_text[end:end + 2]
|
||||
@@ -461,7 +470,6 @@ def extract_era_years(text):
|
||||
ganzhi = None
|
||||
covered.update(range(start, end))
|
||||
raw_fan = original_text[start:end]
|
||||
gregorian = gy_year if gy_year is not None else 1911 + year_num
|
||||
result = {
|
||||
"原文": raw_fan,
|
||||
"原文(简体)": simplified_text[start:end],
|
||||
@@ -503,6 +511,9 @@ def extract_era_years(text):
|
||||
break
|
||||
if gy_year is not None and not (1000 <= gy_year <= 2100):
|
||||
continue
|
||||
minguo_gy = gy_year if gy_year is not None else computed
|
||||
if not (1000 <= minguo_gy <= 2100):
|
||||
continue
|
||||
start, end = m.start(), m.end()
|
||||
covered.update(range(start, end))
|
||||
raw_fan = original_text[start:end]
|
||||
@@ -528,7 +539,7 @@ def extract_era_years(text):
|
||||
_era_gongyuan = re.compile(
|
||||
r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))?'
|
||||
+ r'(?P<era>' + _known_era_alt + r')'
|
||||
+ r'(?:公元|西元|公历)\s*(?P<gy_year>[〇零○O一二三四五六七八九\d0-9]{3,4})\s*年?'
|
||||
+ r'(?:公元|西元|公历)\s*(?P<gy_year>' + _gz_gy_year + r')\s*年?'
|
||||
+ r'(?P<gy_ganzhi>' + _gz_pair + r')?'
|
||||
)
|
||||
for m in _era_gongyuan.finditer(simplified_text):
|
||||
@@ -657,7 +668,7 @@ def extract_era_years(text):
|
||||
|
||||
# === 2. 公元(允许无"年"字)===
|
||||
gongyuan_pattern = re.compile(
|
||||
r'(?:公元|西元|公历)?\s*([〇零○O一二三四五六七八九\d0-9]{3,4})\s*(?:(.*?))?\s*(年)?'
|
||||
r'(?:公元|西元|公历)?\s*(' + _gz_gy_year + r')\s*(?:(.*?))?\s*(年)?'
|
||||
)
|
||||
for m in gongyuan_pattern.finditer(simplified_text):
|
||||
start, end = m.start(), m.end()
|
||||
@@ -837,6 +848,10 @@ def extract_era_years(text):
|
||||
return
|
||||
year_text = year_override if year_override is not None else m.group("year")
|
||||
year_num = chinese_to_num(year_text)
|
||||
# 年数上界:年号纪年不可能出现"洪武146356"这类(那是被当年份的数字 ID/编号),
|
||||
# 年数 > 999 一律不认——否则会算出 147723 这种荒诞公元
|
||||
if not (1 <= year_num <= 999):
|
||||
return
|
||||
base = None
|
||||
multi = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user