HNFファイルを表示するスクリプト
MovableTypeなどのブログシステムより前に利用していた「ハイパー日記システム」。今はサーバーのPerlに対応できなくなり動作していませんが,サイト内全文検索(Namazu)の検索対象には入れているので,検索結果のリンクがエラーにならないよう対策を講じました。Geminiとやり取りしながら.nhfファイルを表示するPHPのスクリプトを生成。該当日付のファイル内容を表示するだけでなく,その月の日記の一覧をリンク表示したり,前後の月に移動したりする機能も持たせました。また,アクセスの際にURLパラメータがなければ,最新の日記を表示するようにもしました。だだし,かなり昔に使用していたシステムなので,最新のデータとして表示されるは2004年07月23日になります。
◆サイト内全文検索(Namazu)の検索結果からアクセス
http://freeside.skr.jp/diary/?200204a#200204050 (126 bytes)
→ /home/(ユーザー名)/tdiary/2004/d20020405.hnf の内容を表示
◆URLパラメータなしで,スクリプト(index.php)にアクセス
http://freeside.skr.jp/diary/
→ 最新の日記(2004/07/23)を表示
<?php
/**
* HNF Diary System (Simplified Version)
* .hnfファイルを読み込み、カレンダーと本文を表示するPHPスクリプト
*/
// 出力はすべてEUC-JPとしてブラウザに通知
header('Content-Type: text/html; charset=EUC-JP');
/**
* UTF-8の文字列をEUC-JPに変換して出力する補助関数
*/
function e($str) {
return mb_convert_encoding($str, "EUC-JP", "UTF-8");
}
$baseDir = "/home/(ユーザー名)/diary";
$query = $_SERVER['QUERY_STRING'] ?? '';
// --- 年月の特定ロジック ---
if (empty($query) || !preg_match('/^[0-9]{6}/', $query)) {
$foundYM = "";
$years = glob($baseDir . '/[0-9][0-9][0-9][0-9]', GLOB_ONLYDIR);
if ($years) {
rsort($years);
foreach ($years as $yPath) {
$y = basename($yPath);
for ($m = 12; $m >= 1; $m--) {
$mStr = sprintf('%02d', $m);
$files = glob("{$yPath}/d{$y}{$mStr}[0-9][0-9].hnf");
if (!empty($files)) {
$foundYM = $y . $mStr;
break 2;
}
}
}
}
$yearMonth = $foundYM ?: date('Ym');
} else {
$yearMonth = substr($query, 0, 6);
}
$year = substr($yearMonth, 0, 4);
$month = substr($yearMonth, 4, 2);
// --- 1. JavaScriptによる日付自動判定 ---
echo "<html><head>";
// スマホ用メタタグの挿入
echo '<meta name="viewport" content="width=device-width,initial-scale=1.0">';
echo "<script>
const h = window.location.hash;
if (h && h.length >= 9) {
const hContent = h.replace('#', '');
const correctDay = hContent.substring(6, 8);
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('day') !== correctDay) {
urlParams.set('day', correctDay);
const baseQ = window.location.search ? window.location.search.substring(1).split('&')[0] : '{$yearMonth}c';
window.location.replace(window.location.pathname + '?' + baseQ + '&day=' + correctDay + h);
}
}
</script>";
// --- 2. 表示対象日の決定 ---
$day = $_GET['day'] ?? '';
$day = substr($day, 0, 2);
// その月のファイルが一つでもあるか確認
$monthFiles = glob("{$baseDir}/{$year}/d{$yearMonth}[0-9][0-9].hnf");
$hasDiary = !empty($monthFiles);
if (empty($day)) {
for ($d = 31; $d >= 1; $d--) {
$checkDay = sprintf('%02d', $d);
if (file_exists("{$baseDir}/{$year}/d{$yearMonth}{$checkDay}.hnf")) {
$day = $checkDay;
break;
}
}
}
$filePath = "{$baseDir}/{$year}/d{$yearMonth}{$day}.hnf";
// --- HTML出力(headの続きとbody) ---
echo "<title>" . e("{$year}年{$month}月" . ($day ? "{$day}日" : "")) . "</title></head>";
// スマホで文字が小さくなりすぎないよう body に padding 調整
echo "<body style='font-family:sans-serif; background:#fff; line-height:1.6; padding:15px; color:#333; max-width:800px; margin:0 auto;'>";
// --- タイトルの追加(下線を他のhrと同じ1px #cccに設定) ---
echo "<h1 style='font-size:1.5em; text-align:center; border-bottom:1px solid #ccc; padding-bottom:10px; margin-bottom:20px;'>HNF Diary System (Simplified Version)</h1>";
// --- 3. 日記本文の表示セクション ---
if (!$hasDiary) {
// その月の日記が一つもない場合
echo "<div style='margin-bottom:20px; font-weight:bold; color:#666;'>" . e("日記なし") . "</div>";
} elseif (!empty($day) && file_exists($filePath)) {
$content = file_get_contents($filePath);
// URLをリンクに変換(EUC-JP環境を考慮)
$content = preg_replace('{https?://[\w?./%&=+#~!-]+}', '<a href="$0" target="_blank" style="color:#00e; text-decoration:underline;">$0</a>', $content);
$anchorId = $yearMonth . $day . "0";
$content = preg_replace('/^(OK|NEW|LNEW)/m', '<a id="' . $anchorId . '" name="' . $anchorId . '"></a>$1', $content);
echo "<div id='diary-content' style='margin-bottom:20px;'>";
echo "<strong style='font-size:1.1em; color:#000;'>" . e("{$year}年{$month}月{$day}日の日記") . "</strong>";
// preのフォントサイズをスマホで見やすい1rem(約16px)程度に調整
echo "<pre style='white-space: pre-wrap; word-wrap: break-word; font-family: monospace; font-size:1rem; border-top:1px solid #ccc; padding-top:15px; margin-top:10px;'>{$content}</pre>";
echo "</div>";
}
echo "<hr style='border:0; border-top:1px solid #ccc; margin:20px 0;'>";
// --- 4. 月間ナビゲーションと一覧 ---
$currentDate = strtotime("{$year}-{$month}-01");
$prevYM = date('Ym', strtotime("-1 month", $currentDate));
$nextYM = date('Ym', strtotime("+1 month", $currentDate));
echo "<div style='margin-bottom:15px; font-size:0.9em;'>";
echo "<a href='?{$prevYM}c' style='text-decoration:none;'><< " . e(date('Y年m月', strtotime("-1 month", $currentDate))) . "</a> | ";
echo "<strong>" . e("{$year}年{$month}月") . "</strong> | ";
echo "<a href='?{$nextYM}c' style='text-decoration:none;'>" . e(date('Y年m月', strtotime("+1 month", $currentDate))) . " >></a>";
echo "</div><ul style='list-style-type: none; padding-left: 0; font-size:1rem;'>";
if ($hasDiary) {
for ($d = 1; $d <= 31; $d++) {
$dayStr = sprintf('%02d', $d);
$targetFile = "{$baseDir}/{$year}/d{$yearMonth}{$dayStr}.hnf";
if (file_exists($targetFile)) {
$lines = file($targetFile);
$title = "";
foreach ($lines as $line) {
$line = trim($line);
if ($line === "" || $line === "~" || $line === "^") continue;
$title = preg_replace('/^(OK|NEW|LNEW|CAT|SUB|TITLE)\s*/i', '', $line);
if (!empty($title)) break;
}
$title = $title ?: e("無題");
$fw = ($day === $dayStr) ? "font-weight:bold; color:#d33;" : "color:#00e;";
// リンクエリアを広げてスマホでタップしやすく
echo "<li style='margin-bottom:10px; {$fw}'><a href='?{$yearMonth}c&day={$dayStr}#{$yearMonth}{$dayStr}0' style='text-decoration:none; color:inherit; display:block;'>[" . e("{$d}日") . "] {$title}</a></li>";
}
}
}
echo "</ul><hr style='border:0; border-top:1px solid #ccc; margin:20px 0;'></body></html>";
PHP
