Files
ancient_date_parser/parser.py
T
sansen a850c72ab5 兼容性增强
资源数据补全
依赖更新
2026-08-07 16:12:59 +08:00

637 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
import pandas as pd
from opencc import OpenCC
from era_data import dynasty_alias, KNOWN_ERAS, VARIANT_MAP, TEMPLE_ALIASES
cc = OpenCC('t2s')
def normalize_chars(text):
"""OpenCC 之外的异体字/俗字归一化。文本与 CSV 两侧必须走同一归一化,保证一致。"""
for variant, canonical in VARIANT_MAP.items():
text = text.replace(variant, canonical)
return text
# === 中文数字转阿拉伯数字 ===
def chinese_to_num(text):
text = text.replace("卅", "三十").replace("卌", "四十").replace("拾", "十")
if text in ["元", "元年"]:
return 1
chinese_numerals = {
'零': 0, '': 0, '○': 0,
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
'十': 10, '百': 100, '千': 1000,
'壹': 1, '貳': 2, '贰': 2, '叁': 3, '肆': 4, '伍': 5, '陸': 6, '陆': 6, '柒': 7, '捌': 8, '玖': 9, '拾': 10,
'卄': 20, '廿': 20, '卅': 30, '卌': 40,
}
if text in chinese_numerals:
return chinese_numerals[text]
if '千' in text:
parts = text.split('千')
total = chinese_to_num(parts[0]) * 1000 if parts[0] else 1000
if len(parts) > 1 and parts[1]:
total += chinese_to_num(parts[1])
return total
if '百' in text:
parts = text.split('百')
total = chinese_to_num(parts[0]) * 100 if parts[0] else 100
if len(parts) > 1 and parts[1]:
total += chinese_to_num(parts[1])
return total
if '十' in text:
parts = text.split('十')
total = 0
if parts[0] == '':
total += 10
else:
total += chinese_to_num(parts[0]) * 10
if len(parts) > 1 and parts[1]:
total += chinese_to_num(parts[1])
return total
if '廿' in text or '卄' in text:
return 20 + (chinese_numerals.get(text[-1], 0) if len(text) > 1 else 0)
if '卅' in text:
return 30 + (chinese_numerals.get(text[-1], 0) if len(text) > 1 else 0)
# 混合数字(如"二零〇一")
total = 0
for ch in text:
total = total * 10 + chinese_numerals.get(ch, int(ch) if ch.isdigit() else 0)
return total
# 朝代别名见 era_data.py
def normalize_dynasty_name(dynasty):
if not dynasty:
return dynasty
# 先将繁体转简体
simplified = cc.convert(dynasty)
# 查别名表(简体)
for prefix, true_name in dynasty_alias.items():
if simplified.startswith(prefix):
return true_name
# 直接匹配朝代字典(简体)
for known_dynasty in era_dict.keys():
if simplified.startswith(known_dynasty) or known_dynasty.startswith(simplified):
return known_dynasty
return simplified
# === 年号表 ===
def _cc(s):
"""CSV 单元格归一化:空值→'',其余走 OpenCC 繁转简 + 异体字归一"""
if pd.isna(s):
return ''
return normalize_chars(cc.convert(str(s)))
era_df = pd.read_csv("中国年号.csv")
era_df["年号"] = era_df["年号"].map(_cc)
era_df["所属朝代"] = era_df["所属朝代"].map(_cc)
era_df["名号"] = era_df["名号"].map(_cc)
def _safe_span(v):
"""CSV 使用年数列(可能为空或非数字)"""
try:
return int(float(v))
except (TypeError, ValueError):
return None
era_dict = {}
# 年号使用年数:用于干支/年数结果的使用期校验(如庆元仅6年,期内无庚子)
era_span = {}
for _, row in era_df.iterrows():
dynasty, era, start = row["所属朝代"], row["年号"], int(row["公元纪年"])
if not era:
continue
era_dict.setdefault(dynasty, {})[era] = start
span = _safe_span(row["使用年数"])
if span is not None:
era_span.setdefault(dynasty, {}).setdefault(era, span)
# 有效年号白名单 = 手工精选 ∪ CSV 年号(自动补全:白名单不应小于数据表)。
# 排除:民国(有专门匹配段)、带括号的年号(如"(后元)",防止与东汉"中元"等歧义)
_csv_eras = {
e for e in era_df["年号"]
if e and e != '民国' and '(' not in e and '' not in e and 2 <= len(e) <= 8
}
KNOWN_ERAS_ALL = KNOWN_ERAS | _csv_eras
# === 庙号表 ===
# 名号列(如"高宗""太宗")表示整个在位期,公元取该名号下最早的年份(即位首年)。
# "代宗(景帝)" 等带括号的写法拆成两种叫法都收录;只收祖/宗/帝/王/后/皇结尾(或帝开头)的名号,
# 排除"拖雷""孺子婴"等非庙号条目。
def _is_temple_name(name):
return len(name) >= 2 and (name.startswith('帝') or name.endswith(('祖', '宗', '帝', '王', '后', '皇')))
temple_dict = {}
# 庙号在位年数(该名号下各年号使用年数之和):用于名号+年数结果的使用期校验
temple_span = {}
for _, row in era_df.iterrows():
name, dynasty, start = row["名号"], row["所属朝代"], int(row["公元纪年"])
if not name or not dynasty:
continue
for part in re.split(r'[()()]', name):
part = part.strip()
if not part or not _is_temple_name(part):
continue
part = TEMPLE_ALIASES.get(part, part)
if not _is_temple_name(part):
continue
temple_dict.setdefault(dynasty, {}).setdefault(part, start)
span = _safe_span(row["使用年数"])
if span is not None:
d = temple_span.setdefault(dynasty, {})
d[part] = d.get(part, 0) + span
# === 干支纪年 ===
_tiangan = '甲乙丙丁戊己庚辛壬癸'
_dizhi = '子丑寅卯辰巳午未申酉戌亥'
_ganzhi_to_offset = {}
for i in range(60):
tg = _tiangan[i % 10]
dz = _dizhi[i % 12]
_ganzhi_to_offset[tg + dz] = i # 0-based: 甲子=0
def ganzhi_to_era_year(ganzhi: str) -> int:
"""干支转年数(1-based):甲子=1, 乙丑=2, ..., 癸亥=60"""
return _ganzhi_to_offset.get(ganzhi, -1) + 1
def gregorian_to_ganzhi(year: int) -> str:
"""按项目约定将公元年转换为干支(公元4年为甲子)。"""
offset = (year - 4) % 60
return _tiangan[offset % 10] + _dizhi[offset % 12]
_gz_pair = '[' + _tiangan + '][' + _dizhi + ']'
# === 数字转中文 ===
def arabic_to_chinese_year(num: int) -> str:
digits = "〇一二三四五六七八九"
if num < 0:
return '-' + ''.join(digits[int(d)] for d in str(abs(num)))
return ''.join(digits[int(d)] for d in str(num))
# === 古代年号正则 ===
# 从 era_dict 构建朝代匹配组(按长度降序排列,优先匹配长朝代名如"北宋"而非"北"
# 额外添加常见但不在CSV中的朝代简称(如"汉"
# 合并 temple_dict 的键:周/秦等年号列全空的朝代也必须有匹配能力
_extra_dynasty_names = ['汉', '晋', '宋', '周']
_dynasty_names = sorted(
list(era_dict.keys()) + list(temple_dict.keys())
+ [d for d in _extra_dynasty_names if d not in era_dict and d not in temple_dict],
key=len, reverse=True
)
_dynasty_alt = '|'.join(re.escape(d) for d in _dynasty_names)
# 常见朝代前缀(大、皇等),用于匹配"大唐""大清"等
_dynasty_prefix = '大皇前后胡'
# 非元年数字字符(排除元,避免歧义:建元元年中第一个元是年号的一部分)。
# 含阿拉伯/全角数字("康熙20年""乾隆20年")与大写数字("道光贰拾年")。
_year_digits = '〇零一二三四五六七八九十廿卄卅卌' + '01234567890123456789' + '壹貳贰叁肆伍陸陆柒捌玖拾'
# 已知年号白名单见 era_data.py
# 预编译已知年号正则片段(按长度降序,优先匹配长年号如"太平兴国"而非"太平"
_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 + ']'
def _build_pattern(with_dynasty=True):
"""构建年号正则:已知年号优先 + 通用兜底(仅限有朝代前缀时)"""
if with_dynasty:
# 朝代前缀模式:加(?<![一-鿿])防止"元"在"开元"中被当朝代
dynasty_part = r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))'
boundary = ''
else:
dynasty_part = r'(?P<dynasty>)'
# 无朝代模式不加边界(允许年号跟在其他汉字后面,如"永乐大帝在永乐三年")
boundary = ''
if with_dynasty:
# 有朝代前缀:年号后直接跟年份数字,无需额外约束
era_year = _known_era_group + r'(?P<year>' + _yd + r'+)年'
era_yuan = _known_era_group + r'元年'
patterns = [
re.compile(dynasty_part + boundary + era_year),
re.compile(dynasty_part + boundary + era_yuan),
# 通用兜底(2-8字)
re.compile(dynasty_part + boundary + r'(?P<era>[一-鿿]{2,8})' + r'(?P<year>' + _yd + r'+)年'),
re.compile(dynasty_part + boundary + r'(?P<era>[一-鿿]{2,8})元年'),
]
else:
# 无朝代前缀:年号后必须紧跟年份数字(前瞻断言),防止"永乐大帝"误匹配
_year_ahead = r'(?=' + _yd + r'+年|元年)'
era_year = _known_era_group + _year_ahead + r'(?P<year>' + _yd + r'+)年'
era_yuan = _known_era_group + _year_ahead + r'元年'
patterns = [
re.compile(dynasty_part + boundary + era_year),
re.compile(dynasty_part + boundary + era_yuan),
]
return patterns
# 无朝代模式只匹配已知年号(避免误匹配如"国"被当年号)
_patterns_no_dynasty = _build_pattern(with_dynasty=False)
# 有朝代模式:已知年号 + 通用兜底
_patterns_dynasty = _build_pattern(with_dynasty=True)
# === 主函数 ===
def extract_era_years(text):
# 去除标记用的方括号(用户标注用,非原文内容)
text = text.replace('[', '').replace(']', '')
original_text = text
simplified_text = normalize_chars(cc.convert(text))
results = []
# 覆盖集:先匹配的段落占据位置,后续段落跳过重叠(防止同一文本被多段重复摘出)
covered = set()
# === 1. 民国 ===
minguo_pattern = re.compile(r'民[国國]\s*([〇零一二三四五六七八九十百廿卄卅卌元\d0-9壹貳贰叁肆伍陸陆柒捌玖拾]+)\s*年')
for m in minguo_pattern.finditer(simplified_text):
year_text = m.group(1)
year_num = chinese_to_num(year_text)
start, end = m.start(), m.end()
covered.update(range(start, end))
raw_fan = original_text[start:end]
results.append({
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": "民国",
"年号": "民国纪年",
"年数": year_num,
"公元": 1911 + year_num,
"公元中文": f"{arabic_to_chinese_year(1911 + year_num)}年",
"位置": {"起始": start, "结束": end},
"类型": "民国纪年",
})
# 1b. 民国+干支纪年(如"民国丁卯""民国甲戌"
minguo_gz = re.compile(r'民[国國]\s*(?P<ganzhi>' + _gz_pair + r')')
for m in minguo_gz.finditer(simplified_text):
ganzhi = m.group("ganzhi")
gz_offset = _ganzhi_to_offset.get(ganzhi, -1)
if gz_offset < 0:
continue
# 民国元年=1912, base_gz = (1912-4)%60 = 8 (己酉)
base_gz = (1912 - 4) % 60
year_in_era = ((gz_offset - base_gz) % 60) + 1
gregorian = 1912 + year_in_era - 1
start, end = m.start(), m.end()
covered.update(range(start, end))
raw_fan = original_text[start:end]
results.append({
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": "民国",
"年号": "民国纪年",
"年数": year_in_era,
"干支": ganzhi,
"公元": gregorian,
"公元中文": f"{arabic_to_chinese_year(gregorian)}年",
"位置": {"起始": start, "结束": end},
"类型": "民国纪年(干支)",
})
# === 2b. 年号+公元(如"清康熙公元一六八七年丁卯") ===
# 年号后直接给出公元年份:年数=公元-年号起始+1,与"年号+年数"公式自洽;
# 紧跟的干支用于校验年份一致性。置于公元段之前,合并结果优先、避免重复摘出。
_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一二三四五六七八九\d-]{3,4})\s*年?'
+ r'(?P<gy_ganzhi>' + _gz_pair + r')?'
)
for m in _era_gongyuan.finditer(simplified_text):
start, end = m.start(), m.end()
if any(pos in covered for pos in range(start, end)):
continue
era = cc.convert(m.group("era"))
year_num = chinese_to_num(m.group("gy_year"))
if not (1000 <= year_num <= 2100):
continue
dynasty = normalize_dynasty_name(m.group("dynasty") or "")
base = None
multi = []
if dynasty:
for d, eras in era_dict.items():
if dynasty in d or d in dynasty:
if era in eras:
base = eras[era]
dynasty = d
break
if base is None:
for d, eras in era_dict.items():
if era in eras:
multi.append((d, eras[era]))
if multi:
dynasty, base = multi[0]
if base is None:
continue
covered.update(range(start, end))
year_in_era = year_num - base + 1
ganzhi = m.group("gy_ganzhi")
result = {
"原文": original_text[start:end],
"原文(简体)": m.group(0),
"朝代": dynasty,
"年号": era,
"年数": year_in_era,
"公元": year_num,
"公元中文": f"{arabic_to_chinese_year(year_num)}年",
"位置": {"起始": start, "结束": end},
"类型": "年号+公元",
}
if len(multi) > 1:
result["多候选"] = True
result["候选"] = [list(c) for c in multi]
# 公元超出年号使用期(如"康熙公元1950年")→ 标注,值保留
span = era_span.get(dynasty, {}).get(era)
if span is not None and year_num > base + span - 1:
result["超出使用期"] = True
# 紧跟的干支与公元年份不符(如"一六八九年丁卯",1689应为己巳)→ 标注
if ganzhi:
result["干支"] = ganzhi
if gregorian_to_ganzhi(year_num) != ganzhi:
result["干支不符"] = True
results.append(result)
# === 2. 公元(允许无"年"字)===
gongyuan_pattern = re.compile(
r'(公元)?\s*([〇零○O一二三四五六七八九\d-]{3,4})\s*(?:.*?)?\s*(年)?'
)
for m in gongyuan_pattern.finditer(simplified_text):
start, end = m.start(), m.end()
if any(pos in covered for pos in range(start, end)):
continue
year_text = m.group(2)
year_num = chinese_to_num(year_text)
if not (1000 <= year_num <= 2100):
continue
covered.update(range(start, end))
raw_fan = original_text[start:end]
era_type = "公元纪年(中华人民共和国)" if year_num >= 1949 else "公元纪年(近代/其他)"
results.append({
"原文": raw_fan,
"原文(简体)": m.group(0),
"类型": era_type,
"公元": year_num,
"公元中文": f"{arabic_to_chinese_year(year_num)}年",
"位置": {"起始": start, "结束": end},
})
# === 3. 干支纪年(如"清雍正乙巳十一月十二午时生")===
def _process_ganzhi_match(m):
"""处理干支纪年匹配"""
raw_dynasty = m.group("dynasty") if "dynasty" in m.groupdict() else ""
dynasty = normalize_dynasty_name(raw_dynasty or "")
era = cc.convert(m.group("era"))
ganzhi = m.group("ganzhi")
if ganzhi not in _ganzhi_to_offset:
return
# 查找朝代和基准年
base = None
multi = []
if dynasty:
for d, eras in era_dict.items():
if dynasty in d or d in dynasty:
if era in eras:
base = eras[era]
dynasty = d
break
if not base:
# 无朝代前缀:收集全部候选(如"大定"=北周581/金1161),取首个但标注歧义
for d, eras in era_dict.items():
if era in eras:
multi.append((d, eras[era]))
if multi:
dynasty, base = multi[0]
if not base:
return
# 干支60年循环:计算基准年的干支位置,再求偏移
base_gz = (base - 4) % 60 # 公元4年=甲子(0),所以偏移=4
gz_offset = _ganzhi_to_offset.get(ganzhi, 0) # 0-based
year_in_era = ((gz_offset - base_gz) % 60) + 1 # 1-based年数
gregorian = base + year_in_era - 1
start, end = m.start(), m.end()
if any(pos in covered for pos in range(start, end)):
return
covered.update(range(start, end))
raw_fan = original_text[start:end]
result = {
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": dynasty,
"年号": era,
"年数": year_in_era,
"干支": ganzhi,
"公元": gregorian,
"公元中文": f"{arabic_to_chinese_year(gregorian)}年",
"位置": {"起始": start, "结束": end},
"类型": "干支纪年",
}
if len(multi) > 1:
result["多候选"] = True
result["候选"] = [list(c) for c in multi]
# 使用期校验:换算结果超出年号实际使用年数(如"庆元庚子"→1240,庆元仅11951200)。
# 值保留,仅标注,供人工判断(碑刻原数据常与年号实际期间不符)。
span = era_span.get(dynasty, {}).get(era)
if span is not None and gregorian > base + span - 1:
result["超出使用期"] = True
results.append(result)
# 模式A:有朝代前缀(如"清雍正乙巳")
# 单字年号加前瞻(?![地支]),防止单字年号"丙"吞掉天干"丙午"中的"丙"
_gz_era_group = r'(?P<era>' + '|'.join(
(re.escape(e) + (r'(?![子丑寅卯辰巳午未申酉戌亥])' if len(e) == 1 else ''))
for e in sorted(KNOWN_ERAS_ALL, key=len, reverse=True)
) + r')'
ganzhi_dynasty = re.compile(
r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))'
+ _gz_era_group
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
)
for m in ganzhi_dynasty.finditer(simplified_text):
_process_ganzhi_match(m)
# 模式B:无朝代前缀(如"嘉庆癸亥"),年号≥2字符避免天干误匹配
_long_era_names = [e for e in KNOWN_ERAS_ALL if len(e) >= 2]
_long_era_alt = '|'.join(sorted(_long_era_names, key=len, reverse=True))
ganzhi_no_dynasty = re.compile(
r'(?P<dynasty>)'
+ r'(?P<era>' + _long_era_alt + r')'
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
)
for m in ganzhi_no_dynasty.finditer(simplified_text):
_process_ganzhi_match(m)
# === 3b. 庙号纪年(如"宋高宗""清圣祖",指整个在位期,公元=即位首年)===
# 庙号必须带朝代前缀("高宗"在唐/宋/清均有,单独出现有歧义)
_temple_names = sorted({t for d in temple_dict for t in temple_dict[d]}, key=len, reverse=True)
_temple_alt = '|'.join(re.escape(t) for t in _temple_names)
_temple_pattern = re.compile(
r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))'
+ r'(?P<temple>' + _temple_alt + r')'
# 后面紧跟"X年/元年"时不按庙号匹配,交给古代纪年按年号解析(如"元泰定三年")
+ r'(?!(?:' + _yd + r'+年|元年))'
)
for m in _temple_pattern.finditer(simplified_text):
start, end = m.start(), m.end()
if any(pos in covered for pos in range(start, end)):
continue
dynasty = normalize_dynasty_name(m.group("dynasty") or "")
temple = m.group("temple")
base = None
if dynasty:
# 先精确匹配朝代,再模糊匹配(如"宋"→"南宋"
for d in temple_dict:
if d == dynasty and temple in temple_dict[d]:
dynasty, base = d, temple_dict[d][temple]
break
if base is None:
for d in temple_dict:
if (dynasty in d or d in dynasty) and temple in temple_dict[d]:
dynasty, base = d, temple_dict[d][temple]
break
if base is None:
continue
covered.update(range(start, end))
raw_fan = original_text[start:end]
results.append({
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": dynasty,
"庙号": temple,
"公元": base,
"公元中文": f"{arabic_to_chinese_year(base)}年",
"位置": {"起始": start, "结束": end},
"类型": "庙号纪年",
})
# === 4. 古代年号 ===
def _process_era_match(m, year_override=None):
"""处理一个年号正则匹配结果"""
raw_dynasty = m.group("dynasty") if "dynasty" in m.groupdict() else ""
dynasty = normalize_dynasty_name(raw_dynasty or "")
era = cc.convert(m.group("era"))
# 过滤误匹配:年号是单个数字字符(如"民国二十六年"中的"二"
if len(era) == 1 and era in '零一二三四五六七八九十廿卄卅卌':
return
# 过滤误匹配:年号全是数字汉字(如"元一九五二年"中的"一九五"
if era and all(ch in '零一二三四五六七八九十〇' for ch in era):
return
year_text = year_override if year_override is not None else m.group("year")
year_num = chinese_to_num(year_text)
base = None
multi = []
if dynasty:
for d, eras in era_dict.items():
if dynasty in d or d in dynasty:
if era in eras:
base = eras[era]
dynasty = d
break
else:
# 无朝代前缀:收集全部候选(如"建元"=西汉-140/东晋343/南齐479),取首个但标注歧义
candidates = []
for d, eras in era_dict.items():
if era in eras:
candidates.append((d, eras[era]))
if len(candidates) == 1:
dynasty, base = candidates[0]
elif len(candidates) > 1:
dynasty, base = candidates[0]
multi = candidates
if base is None:
# 名号+年数兜底(如"汉惠帝三年"→即位首年-194 + 3 - 1,指皇帝在位第N年)
if dynasty:
for d, temples in temple_dict.items():
if (dynasty in d or d in dynasty) and era in temples:
base = temples[era]
dynasty = d
break
start, end = m.start(), m.end()
raw_fan = original_text[start:end]
covered.update(range(start, end))
if base:
gregorian = base + year_num - 1
result = {
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": dynasty,
"年号": era,
"年数": year_num,
"公元": gregorian,
"公元中文": f"{arabic_to_chinese_year(gregorian)}年",
"位置": {"起始": start, "结束": end},
"类型": "古代纪年",
}
if len(multi) > 1:
result["多候选"] = True
result["候选"] = [list(c) for c in multi]
# 使用期校验:年数结果超出年号使用期(或名号在位年数)时标注,值保留
span = era_span.get(dynasty, {}).get(era)
if span is None:
span = temple_span.get(dynasty, {}).get(era)
if span is not None and gregorian > base + span - 1:
result["超出使用期"] = True
results.append(result)
else:
results.append({
"原文": raw_fan,
"原文(简体)": m.group(0),
"朝代": dynasty,
"年号": era,
"年数": year_num,
"位置": {"起始": start, "结束": end},
"类型": "古代纪年(未匹配)",
})
# 收集所有匹配,对于重叠区间保留覆盖范围更大的(如"金大定元年"优先于"大定元年"
all_matches = []
for pat in _patterns_no_dynasty:
for m in pat.finditer(simplified_text):
has_year = "year" in m.groupdict() and m.group("year") is not None
all_matches.append((m, None if has_year else "元", False))
for pat in _patterns_dynasty:
for m in pat.finditer(simplified_text):
has_year = "year" in m.groupdict() and m.group("year") is not None
all_matches.append((m, None if has_year else "元", True))
# 按覆盖范围降序排列(长的优先),同长度时朝代模式优先
all_matches.sort(key=lambda x: (x[0].end() - x[0].start(), x[2]), reverse=True)
for m, year_override, _is_dynasty in all_matches:
if not any(pos in covered for pos in range(m.start(), m.end())):
_process_era_match(m, year_override=year_override)
for result in results:
if result.get("公元") is not None:
result.setdefault("干支", gregorian_to_ganzhi(result["公元"]))
return results
def convert_text(text):
results = extract_era_years(text)
return results