@@ -3,14 +3,21 @@ import re
import pandas as pd
import pandas as pd
from opencc import OpenCC
from opencc import OpenCC
from era_data import dynasty_alias , KNOWN_ERAS
from era_data import dynasty_alias , KNOWN_ERAS , VARIANT_MAP , TEMPLE_ALIASES
cc = OpenCC ( ' t2s ' )
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 ) :
def chinese_to_num ( text ) :
text = text . replace ( " 卅 " , " 三十 " ) . replace ( " 卌 " , " 四十 " )
text = text . replace ( " 卅 " , " 三十 " ) . replace ( " 卌 " , " 四十 " ) . replace ( " 拾 " , " 十 " )
if text in [ " 元 " , " 元年 " ] :
if text in [ " 元 " , " 元年 " ] :
return 1
return 1
@@ -18,22 +25,36 @@ def chinese_to_num(text):
' 零 ' : 0 , ' 〇 ' : 0 , ' ○ ' : 0 ,
' 零 ' : 0 , ' 〇 ' : 0 , ' ○ ' : 0 ,
' 一 ' : 1 , ' 二 ' : 2 , ' 三 ' : 3 , ' 四 ' : 4 , ' 五 ' : 5 , ' 六 ' : 6 , ' 七 ' : 7 , ' 八 ' : 8 , ' 九 ' : 9 ,
' 一 ' : 1 , ' 二 ' : 2 , ' 三 ' : 3 , ' 四 ' : 4 , ' 五 ' : 5 , ' 六 ' : 6 , ' 七 ' : 7 , ' 八 ' : 8 , ' 九 ' : 9 ,
' 十 ' : 10 , ' 百 ' : 100 , ' 千 ' : 1000 ,
' 十 ' : 10 , ' 百 ' : 100 , ' 千 ' : 1000 ,
' 壹 ' : 1 , ' 貳 ' : 2 , ' 叁 ' : 3 , ' 肆 ' : 4 , ' 伍 ' : 5 , ' 陸 ' : 6 , ' 柒 ' : 7 , ' 捌 ' : 8 , ' 玖 ' : 9 , ' 拾 ' : 10 ,
' 壹 ' : 1 , ' 貳 ' : 2 , ' 贰 ' : 2 , ' 叁 ' : 3 , ' 肆 ' : 4 , ' 伍 ' : 5 , ' 陸 ' : 6 , ' 陆 ' : 6 , ' 柒 ' : 7 , ' 捌 ' : 8 , ' 玖 ' : 9 , ' 拾 ' : 10 ,
' 卄 ' : 20 , ' 廿 ' : 20 , ' 卅 ' : 30 , ' 卌 ' : 40 ,
' 卄 ' : 20 , ' 廿 ' : 20 , ' 卅 ' : 30 , ' 卌 ' : 40 ,
}
}
if text in chinese_numerals :
if text in chinese_numerals :
return chinese_numerals [ text ]
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 :
if ' 十 ' in text :
parts = text . split ( ' 十 ' )
parts = text . split ( ' 十 ' )
total = 0
total = 0
if parts [ 0 ] == ' ' :
if parts [ 0 ] == ' ' :
total + = 10
total + = 10
else :
else :
total + = chinese_numerals . get ( parts [ 0 ] , 0 ) * 10
total + = chinese_to_num ( parts [ 0 ] ) * 10
if len ( parts ) > 1 and parts [ 1 ] :
if len ( parts ) > 1 and parts [ 1 ] :
total + = chinese_numerals . get ( parts [ 1 ] , 0 )
total + = chinese_to_num ( parts [ 1 ] )
return total
return total
if ' 廿 ' in text or ' 卄 ' in text :
if ' 廿 ' in text or ' 卄 ' in text :
@@ -68,14 +89,71 @@ def normalize_dynasty_name(dynasty):
# === 年号表 ===
# === 年号表 ===
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 = pd . read_csv ( " 中国年号.csv " )
era_df [ " 年号 " ] = era_df [ " 年号 " ] . map ( lambda x : cc . convert ( str ( x ) ) )
era_df [ " 年号 " ] = era_df [ " 年号 " ] . map ( _cc )
era_df [ " 所属朝代 " ] = era_df [ " 所属朝代 " ] . map ( lambda x : cc . convert ( str ( x ) ) )
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 = { }
era_dict = { }
# 年号使用年数:用于干支/年数结果的使用期校验(如庆元仅6年,期内无庚子)
era_span = { }
for _ , row in era_df . iterrows ( ) :
for _ , row in era_df . iterrows ( ) :
dynasty , era , start = row [ " 所属朝代 " ] , row [ " 年号 " ] , int ( row [ " 公元纪年 " ] )
dynasty , era , start = row [ " 所属朝代 " ] , row [ " 年号 " ] , int ( row [ " 公元纪年 " ] )
if not era :
continue
era_dict . setdefault ( dynasty , { } ) [ era ] = start
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
# === 干支纪年 ===
# === 干支纪年 ===
@@ -111,21 +189,24 @@ def arabic_to_chinese_year(num: int) -> str:
# === 古代年号正则 ===
# === 古代年号正则 ===
# 从 era_dict 构建朝代匹配组(按长度降序排列,优先匹配长朝代名如"北宋"而非"北")
# 从 era_dict 构建朝代匹配组(按长度降序排列,优先匹配长朝代名如"北宋"而非"北")
# 额外添加常见但不在CSV中的朝代简称(如"汉")
# 额外添加常见但不在CSV中的朝代简称(如"汉")
# 合并 temple_dict 的键:周/秦等年号列全空的朝代也必须有匹配能力
_extra_dynasty_names = [ ' 汉 ' , ' 晋 ' , ' 宋 ' , ' 周 ' ]
_extra_dynasty_names = [ ' 汉 ' , ' 晋 ' , ' 宋 ' , ' 周 ' ]
_dynasty_names = sorted (
_dynasty_names = sorted (
list ( era_dict . keys ( ) ) + [ d for d in _extra_dynasty_names if d not in era_dict ] ,
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
key = len , reverse = True
)
)
_dynasty_alt = ' | ' . join ( re . escape ( d ) for d in _dynasty_names )
_dynasty_alt = ' | ' . join ( re . escape ( d ) for d in _dynasty_names )
# 常见朝代前缀(大、皇等),用于匹配"大唐""大清"等
# 常见朝代前缀(大、皇等),用于匹配"大唐""大清"等
_dynasty_prefix = ' 大皇前后胡 '
_dynasty_prefix = ' 大皇前后胡 '
# 非元年数字字符(排除元,避免歧义:建元元年中第一个元是年号的一部分)
# 非元年数字字符(排除元,避免歧义:建元元年中第一个元是年号的一部分)。
_year_digits = ' 〇零一二三四五六七八九十廿卄卅卌 '
# 含阿拉伯/全角数字("康熙20年""乾隆20年")与大写数字("道光贰拾年")。
_year_digits = ' 〇零一二三四五六七八九十廿卄卅卌 ' + ' 01234567890123456789 ' + ' 壹貳贰叁肆伍陸陆柒捌玖拾 '
# 已知年号白名单见 era_data.py
# 已知年号白名单见 era_data.py
# 预编译已知年号正则片段(按长度降序,优先匹配长年号如"太平兴国"而非"太平")
# 预编译已知年号正则片段(按长度降序,优先匹配长年号如"太平兴国"而非"太平")
_known_era_alt = ' | ' . join ( sorted ( KNOWN_ERAS , key = len , reverse = True ) )
_known_era_alt = ' | ' . join ( sorted ( KNOWN_ERAS_ALL , key = len , reverse = True ) )
_known_era_group = r ' (?P<era> ' + _known_era_alt + r ' ) '
_known_era_group = r ' (?P<era> ' + _known_era_alt + r ' ) '
_yd = ' [ ' + _year_digits + ' ] '
_yd = ' [ ' + _year_digits + ' ] '
@@ -173,15 +254,18 @@ def extract_era_years(text):
# 去除标记用的方括号(用户标注用,非原文内容)
# 去除标记用的方括号(用户标注用,非原文内容)
text = text . replace ( ' [ ' , ' ' ) . replace ( ' ] ' , ' ' )
text = text . replace ( ' [ ' , ' ' ) . replace ( ' ] ' , ' ' )
original_text = text
original_text = text
simplified_text = cc . convert ( text )
simplified_text = normalize_chars ( cc . convert ( text ) )
results = [ ]
results = [ ]
# 覆盖集:先匹配的段落占据位置,后续段落跳过重叠(防止同一文本被多段重复摘出)
covered = set ( )
# === 1. 民国 ===
# === 1. 民国 ===
minguo_pattern = re . compile ( r ' 民[国國] \ s*([〇零一二三四五六七八九十百廿卄卅卌元 \ d]+) \ s*年 ' )
minguo_pattern = re . compile ( r ' 民[国國] \ s*([〇零一二三四五六七八九十百廿卄卅卌元 \ d0 -9壹貳贰叁肆伍陸陆柒捌玖拾 ]+) \ s*年 ' )
for m in minguo_pattern . finditer ( simplified_text ) :
for m in minguo_pattern . finditer ( simplified_text ) :
year_text = m . group ( 1 )
year_text = m . group ( 1 )
year_num = chinese_to_num ( year_text )
year_num = chinese_to_num ( year_text )
start , end = m . start ( ) , m . end ( )
start , end = m . start ( ) , m . end ( )
covered . update ( range ( start , end ) )
raw_fan = original_text [ start : end ]
raw_fan = original_text [ start : end ]
results . append ( {
results . append ( {
" 原文 " : raw_fan ,
" 原文 " : raw_fan ,
@@ -207,6 +291,7 @@ def extract_era_years(text):
year_in_era = ( ( gz_offset - base_gz ) % 60 ) + 1
year_in_era = ( ( gz_offset - base_gz ) % 60 ) + 1
gregorian = 1912 + year_in_era - 1
gregorian = 1912 + year_in_era - 1
start , end = m . start ( ) , m . end ( )
start , end = m . start ( ) , m . end ( )
covered . update ( range ( start , end ) )
raw_fan = original_text [ start : end ]
raw_fan = original_text [ start : end ]
results . append ( {
results . append ( {
" 原文 " : raw_fan ,
" 原文 " : raw_fan ,
@@ -221,16 +306,82 @@ def extract_era_years(text):
" 类型 " : " 民国纪年(干支) " ,
" 类型 " : " 民国纪年(干支) " ,
} )
} )
# === 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一二三四五六七八九 \ d0 -9 ] { 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. 公元(允许无"年"字)===
# === 2. 公元(允许无"年"字)===
gongyuan_pattern = re . compile (
gongyuan_pattern = re . compile (
r ' (公元)? \ s*([〇零○O一二三四五六七八九 \ d] { 3,4}) \ s*(?:( .*?) )? \ s*(年)? '
r ' (公元)? \ s*([〇零○O一二三四五六七八九 \ d0 -9 ] { 3,4}) \ s*(?:( .*?) )? \ s*(年)? '
)
)
for m in gongyuan_pattern . finditer ( simplified_text ) :
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_text = m . group ( 2 )
year_num = chinese_to_num ( year_text )
year_num = chinese_to_num ( year_text )
if not ( 1000 < = year_num < = 2100 ) :
if not ( 1000 < = year_num < = 2100 ) :
continue
continue
start , end = m . start ( ) , m . end ( )
covered . update ( range ( start , end ) )
raw_fan = original_text [ start : end ]
raw_fan = original_text [ start : end ]
era_type = " 公元纪年(中华人民共和国) " if year_num > = 1949 else " 公元纪年(近代/其他) "
era_type = " 公元纪年(中华人民共和国) " if year_num > = 1949 else " 公元纪年(近代/其他) "
results . append ( {
results . append ( {
@@ -243,7 +394,6 @@ def extract_era_years(text):
} )
} )
# === 3. 干支纪年(如"清雍正乙巳十一月十二午时生")===
# === 3. 干支纪年(如"清雍正乙巳十一月十二午时生")===
covered = set ( )
def _process_ganzhi_match ( m ) :
def _process_ganzhi_match ( m ) :
""" 处理干支纪年匹配 """
""" 处理干支纪年匹配 """
raw_dynasty = m . group ( " dynasty " ) if " dynasty " in m . groupdict ( ) else " "
raw_dynasty = m . group ( " dynasty " ) if " dynasty " in m . groupdict ( ) else " "
@@ -255,6 +405,7 @@ def extract_era_years(text):
# 查找朝代和基准年
# 查找朝代和基准年
base = None
base = None
multi = [ ]
if dynasty :
if dynasty :
for d , eras in era_dict . items ( ) :
for d , eras in era_dict . items ( ) :
if dynasty in d or d in dynasty :
if dynasty in d or d in dynasty :
@@ -263,11 +414,12 @@ def extract_era_years(text):
dynasty = d
dynasty = d
break
break
if not base :
if not base :
# 无朝代前缀:收集全部候选(如"大定"=北周581/金1161),取首个但标注歧义
for d , eras in era_dict . items ( ) :
for d , eras in era_dict . items ( ) :
if era in eras :
if era in eras :
base = eras [ era ]
multi . append ( ( d , eras [ era ] ) )
dynasty = d
if multi :
break
dynasty , base = multi [ 0 ]
if not base :
if not base :
return
return
@@ -281,7 +433,7 @@ def extract_era_years(text):
return
return
covered . update ( range ( start , end ) )
covered . update ( range ( start , end ) )
raw_fan = original_text [ start : end ]
raw_fan = original_text [ start : end ]
results . append ( {
result = {
" 原文 " : raw_fan ,
" 原文 " : raw_fan ,
" 原文(简体) " : m . group ( 0 ) ,
" 原文(简体) " : m . group ( 0 ) ,
" 朝代 " : dynasty ,
" 朝代 " : dynasty ,
@@ -292,13 +444,22 @@ def extract_era_years(text):
" 公元中文 " : f " { arabic_to_chinese_year ( gregorian ) } 年 " ,
" 公元中文 " : f " { arabic_to_chinese_year ( gregorian ) } 年 " ,
" 位置 " : { " 起始 " : start , " 结束 " : end } ,
" 位置 " : { " 起始 " : start , " 结束 " : end } ,
" 类型 " : " 干支纪年 " ,
" 类型 " : " 干支纪年 " ,
} )
}
if len ( multi ) > 1 :
result [ " 多候选 " ] = True
result [ " 候选 " ] = [ list ( c ) for c in multi ]
# 使用期校验:换算结果超出年号实际使用年数(如"庆元庚子"→1240,庆元仅1195– 1200)。
# 值保留,仅标注,供人工判断(碑刻原数据常与年号实际期间不符)。
span = era_span . get ( dynasty , { } ) . get ( era )
if span is not None and gregorian > base + span - 1 :
result [ " 超出使用期 " ] = True
results . append ( result )
# 模式A:有朝代前缀(如"清雍正乙巳")
# 模式A:有朝代前缀(如"清雍正乙巳")
# 单字年号加前瞻(?![地支]),防止单字年号"丙"吞掉天干"丙午"中的"丙"
# 单字年号加前瞻(?![地支]),防止单字年号"丙"吞掉天干"丙午"中的"丙"
_gz_era_group = r ' (?P<era> ' + ' | ' . join (
_gz_era_group = r ' (?P<era> ' + ' | ' . join (
( re . escape ( e ) + ( r ' (?![子丑寅卯辰巳午未申酉戌亥]) ' if len ( e ) == 1 else ' ' ) )
( re . escape ( e ) + ( r ' (?![子丑寅卯辰巳午未申酉戌亥]) ' if len ( e ) == 1 else ' ' ) )
for e in sorted ( KNOWN_ERAS , key = len , reverse = True )
for e in sorted ( KNOWN_ERAS_ALL , key = len , reverse = True )
) + r ' ) '
) + r ' ) '
ganzhi_dynasty = re . compile (
ganzhi_dynasty = re . compile (
r ' (?P<dynasty>(?:(?<![一-鿿])[ ' + _dynasty_prefix + r ' ](?: ' + _dynasty_alt + r ' ))|(?:(?<![一-鿿]) ' + _dynasty_alt + r ' )) '
r ' (?P<dynasty>(?:(?<![一-鿿])[ ' + _dynasty_prefix + r ' ](?: ' + _dynasty_alt + r ' ))|(?:(?<![一-鿿]) ' + _dynasty_alt + r ' )) '
@@ -309,7 +470,7 @@ def extract_era_years(text):
_process_ganzhi_match ( m )
_process_ganzhi_match ( m )
# 模式B:无朝代前缀(如"嘉庆癸亥"),年号≥2字符避免天干误匹配
# 模式B:无朝代前缀(如"嘉庆癸亥"),年号≥2字符避免天干误匹配
_long_era_names = [ e for e in KNOWN_ERAS if len ( e ) > = 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 ) )
_long_era_alt = ' | ' . join ( sorted ( _long_era_names , key = len , reverse = True ) )
ganzhi_no_dynasty = re . compile (
ganzhi_no_dynasty = re . compile (
r ' (?P<dynasty>) '
r ' (?P<dynasty>) '
@@ -319,6 +480,49 @@ def extract_era_years(text):
for m in ganzhi_no_dynasty . finditer ( simplified_text ) :
for m in ganzhi_no_dynasty . finditer ( simplified_text ) :
_process_ganzhi_match ( m )
_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. 古代年号 ===
# === 4. 古代年号 ===
def _process_era_match ( m , year_override = None ) :
def _process_era_match ( m , year_override = None ) :
@@ -336,6 +540,7 @@ def extract_era_years(text):
year_text = year_override if year_override is not None else m . group ( " year " )
year_text = year_override if year_override is not None else m . group ( " year " )
year_num = chinese_to_num ( year_text )
year_num = chinese_to_num ( year_text )
base = None
base = None
multi = [ ]
if dynasty :
if dynasty :
for d , eras in era_dict . items ( ) :
for d , eras in era_dict . items ( ) :
@@ -345,6 +550,7 @@ def extract_era_years(text):
dynasty = d
dynasty = d
break
break
else :
else :
# 无朝代前缀:收集全部候选(如"建元"=西汉-140/东晋343/南齐479),取首个但标注歧义
candidates = [ ]
candidates = [ ]
for d , eras in era_dict . items ( ) :
for d , eras in era_dict . items ( ) :
if era in eras :
if era in eras :
@@ -353,13 +559,22 @@ def extract_era_years(text):
dynasty , base = candidates [ 0 ]
dynasty , base = candidates [ 0 ]
elif len ( candidates ) > 1 :
elif len ( candidates ) > 1 :
dynasty , base = candidates [ 0 ]
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 ( )
start , end = m . start ( ) , m . end ( )
raw_fan = original_text [ start : end ]
raw_fan = original_text [ start : end ]
covered . update ( range ( start , end ) )
covered . update ( range ( start , end ) )
if base :
if base :
gregorian = base + year_num - 1
gregorian = base + year_num - 1
results . append ( {
result = {
" 原文 " : raw_fan ,
" 原文 " : raw_fan ,
" 原文(简体) " : m . group ( 0 ) ,
" 原文(简体) " : m . group ( 0 ) ,
" 朝代 " : dynasty ,
" 朝代 " : dynasty ,
@@ -369,7 +584,17 @@ def extract_era_years(text):
" 公元中文 " : f " { arabic_to_chinese_year ( gregorian ) } 年 " ,
" 公元中文 " : f " { arabic_to_chinese_year ( gregorian ) } 年 " ,
" 位置 " : { " 起始 " : start , " 结束 " : end } ,
" 位置 " : { " 起始 " : 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 :
else :
results . append ( {
results . append ( {
" 原文 " : raw_fan ,
" 原文 " : raw_fan ,