前回の解析ジョイントは、サイトの新着情報などから、HTMLのヘディングを取得する、というかなり苦しい状況を想定しました。そのため、どの記事のリンクも同じになってしまいますし、公開時間が取得時間になってしまってます。
同じHTMLのみのサイトでも、もう少し構造的な情報が提供されていれば、RSSと遜色ないデータ取得が可能です。
そのサンプルとして作ったのが、Linkhtml という解析ジョイントです。d3pipes最新版には含まれています。
取得したい情報はやはり以下の4つです。それを正規表現でなんとか見つけ出します。
'heading' : 見出し
'pubtime' : yyyy-mm-dd等の表記であれば、それを自動的にUnixTimestampに変換します
'link' : 見出しを囲む<a>タグからリンクを抽出します。
'fingerprint' : ここではlinkと同じです。
Linkhtmlを使ったパイプの構成例はこんな感じになります。
0 外部から取得 snoopy (対象ページのURI)
10 コード変換(UTF8へ) mbstring (対象ページのエンコーディング)
20 XML解析 linkhtml #([0-9/]{10}).*href=\"([^"]+)\" \>(.*)\</a\>#iU
30 コード変換(UTF8から) mbstring EUC-JP (内部エンコーディング)
40 ローカル保存 moduledb 86400
XOOPSでは伝統的に、日本語はjapanese、英語はenglishとしてきましたが、エンコーディング情報が入っていないために、混乱を招いてきました。
XoopsCubeでは、minahitoさんの提唱で、
(言語名)_(エンコーディング) ※言語名は2文字。全部小文字。
XOOPS (Cube2.1も) でinstallフォルダを残しておくと、管理画面で警告が出ます。
というのも、installフォルダを経由すれば、DBパスワードなどがダダもれになってしまうからです。
ただ現実に、アップグレード時にintallフォルダもアップロードしてしまうとか、installフォルダを簡単なリネーム(_installとか)だけして放置しておく、なんてことは当然考えられます。
その対策って、とても簡単であることにふと気づきました。
'_INSTALL_CHARSET' という定数が定義された状態で、mainfile.php を実行されることは、インストーラ以外にないのです。この定数は、X2,本家2.2, XCLegacy 2.1 いずれでも共通なので、そのまま使えます。
だから、Protectorのprecheckにおいて、この定義があった時点で強制終了するだけでOKです。この方法であれば、_install などの簡単なリネームがバレたケースにも対応できます。もし、なんらかの理由で、本当にインストーラを利用したければ、Protectorを一時的に外せば良いでしょう。
少なくともインストール後、Protectorさえちゃんとインストールすれば、後からinstallフォルダをアップロードしてしまう等のミスをやっても情報漏洩はある程度防げます。(インストール途中で放置しちゃったケースでは対応できませんが、まあ、まだサービス開始もしていないサイトならDBパスワードなどを一通り変更して、もう一度最初からやり直せば良いでしょう)
というわけで、Protector-3.03にそのチェックを入れました。定数定義のチェックが1行あるだけなので、速度的な影響はほとんどないはずです。
D3モジュールの言語定数は、XOOPS_ROOT_PATH側に言語定義ファイルを置くことで、各モジュール毎にオーバーライドできる、なんて話を書きましたが、実際に試してみたらかなり使いづらい、というのが実情です。例えば、モジュールがバージョンアップして、言語定義が増えた場合に、それを簡単に更新する術がありません。
また、仮にもCMSなのに、HTTP経由ではちょっとしたメッセージ変更すらできない、なんていうのもあまり格好良くありません。
というわけで、altsys-0.5に、言語定数オーバーライドシステムを実装してみました。その主要構成部は2つ。
- mylangadmin
myblocksadminやmytplsadminと同じく、各モジュールの管理画面から呼び出されるコントローラです。ここで上書きされた言語定数を、定数名ごとにDB(altsys_language_constants)に保存し、XOOPS_ROOT_PATH/cache/ 下にキャッシュとして書き出します。
- D3LanguageManager
モジュール/コアが言語定数を読み込むときに使うシングルトンクラスです。実際には、mylangadminによって作成されたキャッシュがあればそれを優先的に読み込むだけの処理だったりします。
以下のように呼び出します。
require_once XOOPS_TRUST_PATH.'/libs/altsys/class/D3LanguageManager.class.php' ;
$langman =& D3LanguageManager::getInstance() ;
$langman->read( 'main.php' , $mydirname , $mytrustdirname ) ;
使い方の説明はちょっとお休みして、ジョイントの作り方を解説してみます。
他のサイトから更新データを引っ張ってきたい時、業種によってはRSSが提供されていないことも多くあるでしょう。そういうサイトでは、HTMLを解析するしかありません。
逆に言えば、d3pipesであれば、HTMLを解析するジョイントクラスだけを作ればいいのです。取得もアグリゲーションも絞り込みも保存も表示も、そのまま使えます。
※どんなサイトもRSS化する、なんてサービスもありますが、それだと細かい所に手が届きません。サイト個別に対応するなら、専用の解析ジョイントを書くのが一番です。
さっそく作ってみましょう。今回は解析ジョイントを作るので、クラス定義ファイルの置き場所は、ここになります。
XOOPS_TRUST_PATH/modules/d3pipes/joints/parse/
HTMLからヘディングをとってくるだけなので、Simplehtmlという名前にしましょう。
クラス名は、D3pipesParseSimplehtmlに、ファイル名は、D3pipesParseSimplehtml.class.php となります。
※ クラス名やファイル名は、Camel表記である点に注意が必要です。
クラスファイルの中身では、基本的にexecute()メソッドを記述するだけです。どういうデータを受け取って、どのように返すかは、ジョイントの種別によって違います。解析ジョイントの場合、テキストファイルからエントリ配列を返します。
HTMLを解析する方法にもいろいろありますが、ここでは最もシンプルに正規表現で抜き出します。いろんな抜き出しパターンがあると思われるので、パターン部分はオプションで指定できるようにしましょう。そうすれば、別のサイトにも使い回しが効くかも知れません。
最後に添付したリストを見れば、とっても簡単なことが判るはずです。各エントリに最低限必要な要素は4つです。
'heading' : 見出し
'pubtime' : 発行日時のUnixTimestamp(ここでは、簡易的に取得時間としてますが、HTML内に発行日時の情報があれば、それを解析しても良いでしょう)
'link' : その記事へのリンク(絶対URI)。ここでは定義しようがないので、あとから、Defaultlink という再割り当てジョイントを使います。もし、その記事のURI情報がHTML内にあれば、それをここで割り当てておくべきです。
'fingerprint' : 記事の唯一性を保証するための指紋です。記事が独立したURIを持っていればそれを割り当てますが、ここでは単に見出しのmd5をとってます。
新しく作ったこのジョイントを使ったパイプを構成してみましょう。
そのページの h2 に見出し一覧がある、としてます。
0 外部から取得 snoopy (対象サイトのURI)
10 コード変換(UTF8へ) mbstring (対象サイトのエンコーディング)
20 XML解析 simplehtml #\<h2\>([^<]*)\</h2\>#iU
30 コード変換(UTF8から) mbstring EUC-JP (内部エンコーディング)
40 再割り当て defaultlink (対象サイトのURI)
50 ローカル保存 moduledb 86400