Home » Featured, Headline, プログラミング

OTD掲示板のログをJSONに変換するPHPスクリプト+ビューワを作ってみた

2012 / 4 / 18 コメント募集

OTD掲示板とは、1999年頃からサービスを開始したレンタル掲示板です。デザインのカスタマイズ性が高く、無料でもかなりの改造が可能でした。その後2000年代中頃にライブドアに買収され、昨年2011年にサービスを終了しました。
ログのダウンロードは有料サービスでしたが、サービス終了がアナウンスされた頃には、無料ユーザでもログのダウンロードが可能になっていました。しかし、当時の掲示板にはよくあることですが、ログデータが独自形式のもの。
そこで、過去の遺産を生かせるようにするために、今時のデータ交換フォーマットであるJSONに変換出来るようなスクリプトを作成しました。

事前準備

複数ファイルになっているか。
もともとログのダウンロードが一括で出来るものではなかったはずなので、幾つかのファイルに分けられているはずです。私の場合は300レスごとに1ファイルで、15ファイルありました。PHP側のファイル読み込みのメモリ制限等もあるので、先述のように複数のファイルで読みこむことを前提とした作りになっています。1ファイルだと動かないかもしれません。

UTF-8になっているか。
ログの文字コードがshiftJISだったので、UTF-8に変換しました。
コマンドラインで変換するなり、ツールを使うなりでUTF-8にしたほうが良いでしょう。
Macで変換を行う場合は下記のコマンドで変換できました。
iconv -c -f sjis -t UTF-8 変換前ログ.txt > 変換後ログ.txt

ファイル名は連番になっているか。
ログはファイル名でソートしています。なのでファイル名は連番になっていたほうが良いです。

変換用PHPスクリプト

ファイル:index.php

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>OTD掲示板過去ログ変換スクリプト</title>
    </head>
    <body>
<?php

//各種設定
define('LOG_EXT', 'txt');//ログの拡張子
define('FILE_BYTES', 3000000);//ファイル読み込み用確保メモリ
define('LOG_DIR', './input/');//ログファイルディレクトリ
define('JSON_DIR', './output/');//JSONディレクトリ

//TODO 正規表現もうちょっと。。。
//正規表現データ
$pattern['enter'] = "/^\n/";//改行(ファイル作成環境によって変わる)
$pattern['split'] = "/----------/";//投稿区切り
$pattern['date'] = "/\d{4}\/\d{2}\/\d{2}\s\d{2}:\d{2}/";//日付形式
$pattern['homepage'] = "/HomePage:.*/";//webサイト
$pattern['email'] = "/E-MAIL  :.*/";//メール

/**
 * ファイルリスト読み込み
 * @param string $dir
 * @return array $fileList 
 */
function read_filelist($targetDir, $ext){
    $dir = opendir($targetDir);
    if ($dir) {
        $i = 0;
        $fileList = array();
        while (($file = readdir($dir)) !== false) {
            $path = pathinfo($file);
            if($path['extension'] == $ext){
                $fileList[$i]['dirname'] = $targetDir;
                $fileList[$i]['filename'] = $path['filename'];
                $fileList[$i]['basename'] = $path['basename'];
                $i++;
            }
        } 
        closedir($dir);

        //ファイル名順にソート
        return fileNameSort($fileList);
    }
    else{
        return FALSE;
    }
}

/**
 * ファイル名順にソート
 * @param type $fileList
 * @return type array $fileList
 */
function fileNameSort($fileList){
    $filename_cache = array();
    foreach ($fileList as $value) {
        $filename_cache[] = $value['filename'];
    }
    sort($filename_cache);
    array_multisort($filename_cache, $fileList);
    return $fileList;
}

//ファイルリスト読み込み
$fileList = read_filelist(LOG_DIR, LOG_EXT);

$post_data = array();
foreach ($fileList as $key => $value) {
    
    $fp = fopen($value['dirname'] . '/' . $value['basename'], 'r');
    $i = 0;
    while ($line = fgets($fp, FILE_BYTES)) {

        //空行飛ばし
        if (preg_match($pattern['enter'], $line)) {
            continue;
        }
        //投稿区切り
        if (preg_match($pattern['split'], $line)) {
            $i++;
            $post_data[$i]['count'] = $i;
            continue;
        }
        //投稿ナンバー、タイトル、名前、日付
        elseif (preg_match($pattern['date'], $line)) {
            $split_data = explode('  ', $line);
            $post_data[$i]['post_number'] = trim($split_data[0]);
            $post_data[$i]['title'] = trim($split_data[1]);
            $post_data[$i]['name'] = trim($split_data[2]);
            $post_data[$i]['date'] = trim($split_data[3]);
        }
        //メール
        elseif (preg_match($pattern['email'], $line, $maches)) {
            $post_data[$i]['email'] = str_replace('E-MAIL  :', '', $maches[0]);
        }
        //webサイト
        elseif (preg_match($pattern['homepage'], $line, $maches)) {
            $post_data[$i]['homepage'] = str_replace('HomePage:', '', $maches[0]);
        }
        //本文
        else {
            $post_data[$i]['body'] .= $line;
        }
    }
    fclose($fp);
    unset($fp);
    
    //書き出し
    $output_filePath = JSON_DIR . $value['filename'] . '.json';
    $result = file_put_contents($output_filePath, json_encode($post_data)) ? '書き込み完了' . "\r\n<br />" : '書き込み失敗' . "\r\n<br />";

    //インデックスデータ作成
    $first = reset($post_data);
    $end = end($post_data);        
    $index .= $key . ":{'filename':'" . $value['filename'] . '.json' . "','post_num':'" . $first['post_number'] . "-" . $end['post_number'] . "'},";

    unset($post_data);
    echo $output_filePath . $result;
}

//インデックス書き込み
$index = "var json_files = {" . $index . "};";
$result = file_put_contents(JSON_DIR . "index.js", $index)

?>
    </body>
</html>
ビューワ

ファイル:index.php

<!DOCTYPE html>
<html>
    <head>
        <title>OTD BBS ビューワ</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
        <script type="text/javascript" src="jquery.cookie.js"></script>
        <script type="text/javascript" src="../output/index.js"></script>
        <script type="text/javascript" src="read.js"></script>
        <link rel="stylesheet" type="text/css" href="index.css" />
    </head>
    <body>
        <div id ="top" class="menu">
            <select class="select"></select>
        </div>
        <div id="data"><span class="caution">ロード中...</span></div>
        <div class="menu">
            <a href="#top">一番上に戻る</a>
        </div>
    </body>
</html>

ファイル:read.js

$(function(){

//TODO しおり機能あると便利かも

    var json_files_length = 0;
    for( key in json_files ){ json_files_length++; }

    //cookieに選択中のJSONファイル情報を保存
    if($.cookie('log_filename') == null){
        $.cookie('log_filename', json_files[0]['filename'], { expires: 365 });
        $.cookie('log_post_num', json_files[0]['post_num'], { expires: 365 });
    }
    var selected = '';
    for(var i=0; i < json_files_length; i++){
       selected = ($.cookie('log_filename') == json_files[i]['filename']) ? 'selected' : '';
        $('.select').append($('<option value="' + json_files[i]['filename'] + '" ' + selected + '>' + json_files[i]['post_num'] + '</option>'));
    }
    
    //セレクトアクション時
    $('.select').change(function() {
        $("#data").html('<span class="caution">ロード中...</span>');
        
        //クッキーに選択ファイルを保存
        $.cookie('log_filename', $('.select option:selected').val(), { expires: 365 });
        $.cookie('log_post_num', $('.select option:selected').text(), { expires: 365 });

        $.getJSON('../output/' + $.cookie('log_filename'), function(data){
            $("#data").empty();
            
            // JSONデータ読み込み
            $.each(data, function(j, item){
                
                background = (item.count%2 == 0) ? 'even' : 'odd';
                
                $("#data").append(
                    '<div class="' + background + '">'
                    + '<span id="post' + item.post_number + '">' + item.post_number + '</span>'
                    + ' <span class="title">' + item.title + '</span> '
                    + item.name + ' <span class="sub">' + item.date + '</span><br />'
                    //+ 'メール:' + '<a href="mailto:'+ item.email +'">' + item.email + '</a>' + '<br />' //当時のアドレスが使えるか微妙なので非表示
                    + '<span class="sub">メール: ' + item.email + '</span><br />'
                    + '<span class="sub">Web: ' + '<a href="' + item.homepage + '" target="_blank">' + item.homepage + '</span></a>' + '<br />'
                    + item.body.replace(/(\r\n|\n)/g, '<br />')
                    + '</div>'
                );
            });
        });
    });

    //初期表示用
    $.getJSON('../output/' + $.cookie('log_filename'), function(data){
        
        $("#data").empty();
        
        // JSONデータ読み込み
        $.each(data, function(j, item){
            
            background = (item.count%2 == 0) ? 'even' : 'odd';
            
            $("#data").append(
                '<div class="' + background + '">'
                + '<span id="post' + item.post_number + '">' + item.post_number + '</span>' + ' <span class="title">' + item.title + '</span> '
                + item.name
                + ' <span class="sub">' + item.date + '</span><br />'
                //+ 'メール:' + '<a href="mailto:'+ item.email +'">' + item.email + '</a>' + '<br />' //当時のアドレスが使えるか微妙なので非表示
                + '<span class="sub">メール: ' + item.email + '</span><br />'
                + '<span class="sub">Web: ' + '<a href="' + item.homepage + '" target="_blank">' + item.homepage + '</a></span>' + '<br />'
                + item.body.replace(/\r\n|\n/g, '<br />')
                + '</div>'
            );
        });
    });

});

ファイル:index.css

@charset "utf-8";

body{
    margin: 0;
    display: block;
    
    /* フォント */
    font-family: "メイリオ", "Meiryo", verdana, "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "Osaka", "MS Pゴシック", "MS PGothic", Sans-Serif;
    font-size: medium;
    line-height: 140%;
}

a:link,a:visited{
    color: #2857fc;
    text-decoration:none;
}

a:hover{
    color: #2857fc;
    text-decoration:underline;
}

.menu{
    text-align: right;
    margin: 0;
    padding: 10px;
    background-color: #333;
    color: #FFF;
    font-size: large;
}

#data{
    margin: 0;
    background-color: #CCC;
}

.odd{
    margin: 0;
    padding: 10px;
    background-color: #d1c8b4;
}

.even{
    margin: 0;
    padding: 10px;
    background-color: #c0d1b4;
}

.sub{
    color: #666;
    font-size: small;
}

.title{
    font-weight: bold;
}

.caution{
    font-size:32px;
    font-weight: bold;
    color: #FF0000;
}

※ソースコード表示プラグインの都合でうまく表示出来ていない箇所があるので、コードは下記よりDLして確認してください。

ダウンロード

ダウンロードページからファイル一式をダウンロードできます。jquery.cookie.jsも同梱。ディレクトリ構造のこともあるので、この記事からコピペするよりDLしたほうが都合が良いかもしれません。

利用方法や注意事項

inputディレクトリに過去ログを入れて、index.phpにアクセス。ログの量やファイル数にもよりますが数十秒程度でoutputディレクトリに変換後のログが入ります。あとはviewer/index.htmlにアクセスすれば過去ログを読むことができます。

スクリプトを作る際にテストデータとして私が利用していた掲示板の過去ログしかありませんでしたので、この変換スクリプトであなたの過去ログが確実にJSONに変換できるとは限りませんのであしからず。適当に改造して使って下さい。

変換スクリプトを利用するためにはPHPが動く環境が必要です。Macは最初から入ってます(バージョン管理が面倒なので私はMAMPを利用)。Windowsの場合はXAMPPとか入れればいいはず。その他OSの人は分かっているはずなので割愛します。

ビューワを利用する場合はこのスクリプト以外にjquery.cookie.jsが必要です。
chromeはローカルでのファイル読み込みが出来ませんので、このビューワを使う場合はchrome以外のブラウザで動かして下さい。

ローカルで動かすことを前提で作っているので、セキュリティ関係はガン無視で。

作って公開しといて難なのですが、正規表現が甘かったり、jsがあまり良いコードでなかったりします。もともと公開する予定がなかったので結構適当です。動かなかったら改造してください。
こういったスクリプトは本来perlやpythonのほうが得意そうですけどね。

ライセンス・・・という程でもないのですが、このスクリプトをローカル環境で利用したり、改造して利用する場合については自由です。ただし商用、非商用を問わずネットワーク上でwebサービスとして公開し、利用することは禁じます。


プログラミングPHP 第2版

[`evernote` not found]

どうぞこの記事に関するコメントをお寄せ下さい

Add your comment below, or trackback from your own site. You can also subscribe to these comments via RSS.

Be nice. Keep it clean. Stay on topic. No spam.

これらのタグのみ使用出来ます:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This is a Gravatar-enabled weblog. To get your own globally-recognized-avatar, please register at Gravatar.

*