PHPでテキスト中の和暦・西暦年号を統一する(その2)

(1/1)
PHPでテキスト中の和暦・西暦年号を統一する」では、明治から平成までの近現代に限って西暦を元号に変換できるようにした。
今回は、飛鳥時代の最初の元号「大化」(645年)まで遡って変換できるようにし、さらに、改元の月日(たとえば平成なら1989年1月8日)までチェックして変換するPHPプログラムをつくることにする。

なお、「1000年後の世界」のように、年号なのかテキストなのか、パターンだけでは判別しにくいコンテクストがあるため、「\1000年後の世界」のように年号の前にエスケープ文字を付けることで変換を回避するようにした。

(2023年9月3日)pahooInputDataクラス導入,他

目次

サンプル・プログラムの実行例

PHPでテキスト中の和暦・西暦年号を統一する(その2)

サンプル・プログラムのダウンロード

圧縮ファイルの内容
nengo2.phpサンプル・プログラム本体
pahooNormalizeText.phpテキスト正規化クラス pahooNormalizeText。
テキスト正規化クラスの使い方は「PHPで日本語テキストを正規化」を参照ください。include_pathが通ったディレクトリに配置してください。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
nengo2.php 更新履歴
バージョン 更新日 内容
2.4.0 2023/09/03 pahooInputDataクラス導入,他
2.3 2021/07/25 PHP8対応,リファラ・チェック改良
2.21 2019/04/20 bug-fix: seireki2wareki()
2.2 2019/03/30 リファラチェック機能追加
2.1 2017/12/03 2019年5月1日の改元に対応
pahooNormalizeText.php 更新履歴
バージョン 更新日 内容
4.0 2021/11/03 getRuby_Yahoo():subwordを格納するようにした
3.91 2021/10/07 getRuby_Yahoo():デバッグコードを消去
3.9 2021/09/26 getRuby_Yahoo():ルビ振り(V2)に変更
3.8 2020/07/11 getRuby_Yahoo(): ローマ字も取得
3.71 2020/06/30 http(): each()関数をforeachで代替:PHP7.2対応
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加

解説:準備

  30: //データ入力に関わる関数群:include_pathに配置すること
  31: require_once('pahooInputData.php');
  32: 
  33: //PHPバージョン・チェック
  34: exitIfLessVersion(MINUMUM_VERSION);
  35: 
  36: //リファラチェック+リリースフラグの設定
  37: if (isset($_SERVER['HTTP_HOST']) && ($_SERVER['HTTP_HOST'] == 'localhost')) {
  38:     define('FLAG_RELEASE', FALSE);
  39:     define('REFER_ON', '');
  40:     ini_set('display_errors', 1);
  41:     ini_set('error_reporting', E_ALL);
  42: else {
  43:     //リリース・フラグ(公開時にはTRUEにすること)
  44:     define('FLAG_RELEASE', TRUE);
  45:     //リファラ・チェック(直リン防止用;空文字ならチェックしない)
  46:     if (! isCommandLine()) {
  47:         define('REFER_ON', 'www.pahoo.org');
  48:     } else {
  49:         define('REFER_ON', '');
  50:     }
  51: }
  52: 
  53: //表示幅(ピクセル)
  54: define('WIDTH', 600);
  55: 
  56: //pahooNormalizeText クラス;各自の環境に合わせて変更すること
  57: require_once('pahooNormalizeText.php');
  58: 
  59: //例文
  60: define('DEF_SOUR', "慶応四年一月三日、鳥羽・伏見の戦いが起きる。10月23日、明治天皇が即位。改元は1868年1月25日に遡って適用された。明治2年5月17日、五稜郭が開放され、戊辰戦争は終わる。");
  61: 
  62: //変換関数の選択肢
  63: $SelectFuncs = array(
  64:     //関数名               タイトル             ラジオボタンcheked
  65:     'seireki'   => array('title'=>'西暦に統一',   'checked'=>''),
  66:     'wareki'    => array('title'=>'和暦に統一',   'checked'=>''),
  67:     'mixture'   => array('title'=>'西暦(和暦)', 'checked'=>'')
  68: );
  69: 
  70: //変換範囲の選択肢
  71: $SelectMode = array(
  72:     //範囲               タイトル        ラジオボタンcheked
  73:     2   => array('title'=>'奈良時代まで',  'checked'=>''),
  74:     1   => array('title'=>'近現代まで',    'checked'=>''),
  75:     0   => array('title'=>'変換しない',    'checked'=>'')
  76: );
  77: 
  78: //変換範囲
  79: $ModeEra = 2;

本プログラムは、クラス・ファイル "pahooInputData.php","pahooNormalizeText.php" を利用する。
PHPのクラスについては、「PHPでクラスを使ってテキストの読みやすさを調べる」を参照のこと。

クラス・ファイルの読み込みは組み込み関数  require_once  を用いて行う。パス名は絶対値で指定するか、事前に "php.ini" の include_path で指定されるパスにクラス・ファイルを配置する。

解説:西暦⇔元号変換テーブル

 804: //元号の開始日の西暦yyyymmdd => 元号
 805: //1581年以前はユリウス暦,1582年以降はグレゴリオ暦
 806: var $TABLE_AD_ERA2 = array(
 807: //飛鳥時代
 808: '06450717' => '大化',
 809: '06500322' => '白雉',
 810: '06541124' => '',           //空白期間
 811: '06860814' => '朱鳥',
 812: '06861001' => '',           //空白期間
 813: '07010503' => '大宝',
 814: '07040616' => '慶雲',
 815: '07080207' => '和銅',
 816: //奈良時代
 817: '07151003' => '霊亀',
 818: '07171224' => '養老',
 819: '07240303' => '神亀',

pahooNormalizeTextクラス西暦⇔元号変換テーブルを含んでいる。
添え字は yyyymmdd の8桁で、対になる値(元号)が開始(改元)した西暦年月日(1581年以前はユリウス暦、1582年以降はグレゴリオ暦)を示す。Wikipediaの「元号一覧 (日本)」を参考にした。
元号が空文字になっている期間は元号が定められなかった部分で、元号変換の対象にはならない。

近現代とそれ以前とで変換範囲を分けられるようにするため、テーブルは2種類ある。
なお、南北朝時代については元号が重複するため、南朝の元号を採用した。北朝の元号はコメントアウトしてある。

変換範囲を格納する変数は $MODE_ERA である。

1062: '18610329' => '文久',
1063: '18640327' => '元治',
1064: '18650501' => '慶応',
1065: '18680125' => ''
1066: );

2019年5月1日からの新元号にも対応している。

解説:文字列が元号かどうか判断

1099: /**
1100:  * 文字列が元号かどうか判断する
1101:  * @param   string $str 文字列
1102:  * @return  bool TRUE/FALSE
1103: */
1104: function isera($str) {
1105:     foreach ($this->TABLE_AD_ERA2 as $era) {
1106:         if ($era !'') {
1107:             $pat = '/' . $era . '/ui';
1108:             if (preg_match($pat, $str> 0return TRUE;
1109:         }
1110:     }
1111:     foreach ($this->TABLE_AD_ERA1 as $era) {
1112:         if ($era !'') {
1113:             $pat = '/' . $era . '/ui';
1114:             if (preg_match($pat, $str> 0return TRUE;
1115:         }
1116:     }
1117:     return FALSE;
1118: }

元号から西暦に変換する場合、年号の前に並ぶ文字列が元号かどうかを判断するためのユーザー関数が isera である。
前述の、西暦⇔元号変換テーブルを総なめして判断している。

解説:西暦を元号に変換

1120: /**
1121:  * 西暦を元号に変換する
1122:  * @param   string $prefix 年の前に付いている文字列
1123:  * @param   int $year  年
1124:  * @param   int $month 月(省略可能)
1125:  * @param   int $day   日(省略可能)
1126:  * @return  string 元号(+月日)
1127: */
1128: function ad2era($prefix, $year, $month=0, $day=0) {
1129:     if ($prefix !$this->ESCYEAR) {
1130:         $yyyymmdd = sprintf('%04d%02d%02d', $year, $month, $day);
1131:         $dest = $prefix . $year . '年';
1132:         $flag = FALSE;
1133: 
1134:         if (! $this->isera($prefix)) {
1135:             if (!$flag && ($this->MODE_ERA >2)) {
1136:                 $yy   = '';
1137:                 $last = '';
1138:                 foreach ($this->TABLE_AD_ERA2 as $start=>$era) {
1139:                     if ($yyyymmdd >$start) {
1140:                         $yy   = $year - substr($start, 0, 4);
1141:                         $last = $era;
1142:                     } else if ($last !'') {
1143:                         $str  = ($yy == 0? '元' : (string)($yy + 1);
1144:                         $dest = $prefix . $last . $str . '年';
1145:                         $flag = TRUE;
1146:                         break;
1147:                     }
1148:                 }
1149:             }
1150:             if (!$flag && ($this->MODE_ERA >1)) {
1151:                 $yy   = '';
1152:                 $last = '';
1153:                 foreach ($this->TABLE_AD_ERA1 as $start=>$era) {
1154:                     if ($yyyymmdd >$start) {
1155:                         $yy = ($era == ''? $year - 1 : $year - substr($start, 0, 4);
1156:                         $last = $era;
1157:                     } else if ($last !'') {
1158:                         $str  = ($yy == 0? '元' : (string)($yy + 1);
1159:                         $dest = $prefix . $last . $str . '年';
1160:                         $flag = TRUE;
1161:                         break;
1162:                     }
1163:                 }
1164:             }
1165:         }
1166:     } else {
1167:         $dest = $year . '年';
1168:     }
1169:     if ($month > 0$dest .$month . '月';
1170:     if ($day   > 0$dest .$day   . '日';
1171: 
1172:     return $dest;
1173: }

西暦を元号に変換するユーザー関数が ad2era である。

まず、年号の前の文字 $prefix$ESCYEAR に定義したエスケープ文字かどうかをチェックする(デフォルトでエスケープ文字は \ になっている)。エスケープ文字があれば、変換は行わない。

前述の isera を使い、もし年号の前に接する文字列 $prefix が元号にマッチすれば、それは変換しない。

引数 $month$da は省略可能だが、値が入っている場合には、前述の、西暦⇔元号変換テーブルから改元の月日まで比較し厳密に変換を行う。
また、元号の「1年」は「元年」に置換するようにしている。

解説:元号を西暦に変換

1175: /**
1176:  * 元号を西暦に変換する
1177:  * @param   string $prefix 年の前に付いている文字列(元号)
1178:  * @param   int $year  年
1179:  * @param   int $month 月(省略可能)
1180:  * @param   int $day   日(省略可能)
1181:  * @return  string 西暦(+月日)
1182: */
1183: function era2ad($prefix, $year, $month=0, $day=0) {
1184:     if ($prefix !$this->ESCYEAR) {
1185:         $dest = $prefix . $year . '年';
1186:         if ($this->MODE_ERA >2) {
1187:             foreach ($this->TABLE_AD_ERA2 as $start=>$era) {
1188:                 if (($era !''&& ($era == $prefix)) {
1189:                     $dest = $year + substr($start, 0, 4- 1;
1190:                     $dest .'年';
1191:                     break;
1192:                 }
1193:             }
1194:         }
1195:         if ($this->MODE_ERA >1) {
1196:             foreach ($this->TABLE_AD_ERA1 as $start=>$era) {
1197:                 if (($era !''&& ($era == $prefix)) {
1198:                     $dest = $year + substr($start, 0, 4- 1;
1199:                     $dest .'年';
1200:                     break;
1201:                 }
1202:             }
1203:         }
1204:     } else {
1205:         $dest = $year . '年';
1206:     }
1207:     if ($month > 0)     $dest .$month . '月';
1208:     if ($day   > 0)     $dest .$day   . '日';
1209: 
1210:     return $dest;
1211: }

逆に、元号を西暦に変換するユーザー関数は era2ad である。
処理としては ad2era と似ているが、残念ながら、現時点では改元の月日は西暦で計算している。正確を期すならば、旧暦月日を西暦月日に変換してから境界判断すべきである。

解説:西暦を和暦へ変換

 244: /**
 245:  * 和暦に統一
 246:  * @param   string $sour オリジナル・テキスト
 247:  * @return  string 変換後テキスト
 248: */
 249: function wareki($sour) {
 250:     $pat = '/([^0-9〇一二三四五六七八九十百千万あ-ん、-〟!-¥]{0,4})\s*([0-9元〇一二三四五六七八九十百千万]+)年\s*([0-9〇一二三四五六七八九十]*)月?([0-9〇一二三四五六七八九十]*)日?/msui';
 251: 
 252:     return preg_replace_callback($pat, 'seireki2wareki', $sour);
 253: }

ユーザー関数 wareki は、任意の文字列中の西暦を和暦へ変換する。
正規表現を利用し、テキスト中の元号表記にパターンマッチさせる。
元号の属性は、数字・漢数字・平仮名は含まない4文字以下の文字列であることから、まず [(^0-9〇一二三四五六七八九十百千万あ-ん、-〟!-¥]{0,4}) によって元号部分にマッチさせる。
続く年は、算用数字または漢数字である。月・日も同様だが、この2つの記載は無くてもマッチするようにしている。

パターンマッチと同時に年号の置換処理を行うために、組み込み関数  preg_replace_callback  を利用した。実際に置換を行うのはユーザー関数 seireki2wareki である。

ユーザー関数 seireki2wareki では、「PHPで漢数字を半角数字に変換する(整数版)」で作成した漢数字を数値に変換するユーザー関数 kan2num を呼び出して、漢数字を整数に変換しておく。
そして、前述の ad2era を使って元号に変換する。

解説:和暦を西暦へ変換

 280: /**
 281:  * 西暦に統一
 282:  * @param   string $sour オリジナル・テキスト
 283:  * @return  string 変換後テキスト
 284: */
 285: function seireki($sour) {
 286:     $pat = '/([^0-9〇一二三四五六七八九十百千万あ-ん、-〟!-¥]{0,4})\s*([0-9〇元一二三四五六七八九十百千万]+)年\s*([0-9〇一二三四五六七八九十]*)月?([0-9〇一二三四五六七八九十]*)日?/msui';
 287: 
 288:     return preg_replace_callback($pat, 'wareki2seireki', $sour);
 289: }

 255: /**
 256:  * 和暦→西暦変換(漢数字対応)
 257:  * @param   array $arr 和暦年月日(漢数字可能)
 258:  * @return  string 西暦
 259: */
 260: function wareki2seireki($arr) {
 261:     global $ModeEra;
 262: 
 263:     $pnt = new pahooNormalizeText();    //pahooNormalizeTextクラス
 264:     $pnt->set_mode_era($ModeEra);
 265: 
 266:     $prefix = isset($arr[1]) ? $arr[1: '';
 267: 
 268:     if (isset($arr[2])) {
 269:         if ($arr[2] == '元')    $arr[2] = 1;
 270:     }
 271:     $year   = isset($arr[2]) ? $pnt->kan2num($arr[2], 0: 0;
 272:     $month  = isset($arr[3]) ? $pnt->kan2num($arr[3], 0: 0;
 273:     $day    = isset($arr[4]) ? $pnt->kan2num($arr[4], 0: 0;
 274:     $ad     = $pnt->era2ad($prefix, $year, $month, $day);
 275: 
 276:     $pnt = NULL;
 277:     return $ad;
 278: }

ユーザー関数 seireki は、任意の文字列中の和暦を西暦へ変換する。
年月日を取り出すためのパターンは前述の wareki と同じである。

年号の置換処理を行うために、同様に  preg_replace_callback  を利用し、ユーザー関数 wareki2seireki を呼び出す。
wareki2seireki のロジックも、前述の seireki2wareki とほぼ同じである。

解説:西暦・和暦混合変換

 305: /**
 306:  * 西暦(和暦)混合変換
 307:  * @param   string $sour オリジナル・テキスト
 308:  * @param   int    $mode 変換範囲
 309:  * @return  string 変換後テキスト
 310: */
 311: function mixture($sour) {
 312:     $sour = wareki($sour);      //和暦に統一
 313: 
 314:     $pat = '/([^0-9〇一二三四五六七八九十百千万あ-ん、-〟!-¥]{0,4})([0-9元]+)年/ui';
 315: 
 316:     return preg_replace_callback($pat, 'seireki2mix', $sour);
 317: }

 291: /**
 292:  * 和暦→西暦(和暦)変換
 293:  * @param   array $arr 元号,年
 294:  * @return  string 西暦(和暦)
 295: */
 296: function seireki2mix($arr) {
 297:     $ad = wareki2seireki($arr);
 298:     $wareki = $arr[1. $arr[2. '年';
 299: 
 300:     return ($ad !$wareki? $ad . '(' . $wareki . ')' : $ad;
 301: 
 302:     return $ad . '(' . $arr[1. $arr[2. '年)';
 303: }

ユーザー関数 mixture では、西暦・和暦混合変換を行う。
まず、ユーザー関数 wareki を使って、入力テキスト中の年号を西暦に統一する。

年号の置換処理を行うために、同様に  preg_replace_callback  を利用し、ユーザー関数 seireki2mix を呼び出す。

ユーザー関数 seireki2mix では、前述の wareki2seireki を呼び出して西暦に変換する。
ここで、引数と変換結果が一致しなければ、西暦・和暦を結合した文字列を返す。一致していれば変換せず返す。

質疑応答

【連絡】
「テキスト中の和暦・西暦年号を変換(その2)」を興味深く拝見しました。

次のの文章を変換してみました。
「ペンの日:1935年11月26日、日本ペンクラブ創立。同クラブが1965年に制定」。
設定を「和暦に統一」もしくは、「西暦(和暦)」にすると、「ペンの日:」の「日:」が消えてしまいました。
月・日も変換の際の対象になっている高度なスクリプトのようで、その影響かとも思われますが、一応気が付きましたので報告します。
【回答】
ご指摘のように、関数 wareki の置換表現に問題があることを確認しました。ありがとうございます。
他の置換表現も見直し、2017年12月2日にバージョンアップしました。ご確認下さい。

参考サイト

(この項おわり)
header