このサイトの上下にはスクリーンショットのようなナビゲーションバーが付いて現在地を把握することが出来ます。こういったサイト内で統一されたナビゲーションバーを元になるXMLやHTML文章に直に書いてしまった場合、上位のディレクトリのファイル名が変わったときなどに一気に複数のファイルを書き換える必要が有るため不便です。
そこでこのサイトではPHPでXMLをXHTMLに変換する際に自動生成しています。今回はその方法を紹介します。
get_vd()
//-------------------------------------------------------------------------
// array get_vd( string TargetPath [, string Title] )
// TargetPathはルートからのパス。「/works/fb2k/」など
// Titleは一番最初のタイトルに限りタイトルを直接持ち込める。
//-------------------------------------------------------------------------
get_vd( $target, $title_first = "", $loop = 0 ) {
// 無限ループはまずいので適当にカット
if ($loop > 20) {
exit_with_xml( '無限ループになっています。' );
}
// ディレクトリ名だった場合は/index.xmlを付ける
if( substr( $target, -1, 1 ) == '/' ) {
$target .= 'index.xml';
} elseif ( is_dir( $_SERVER['DOCUMENT_ROOT'] . $target ) ) {
$target .= '/index.xml';
}
$vdir_target = '';
$title_target = '';
// ファイルがあるかチェック。
// 有った場合はvirtual_directoryとタイトルをゲットする
if ( file_exists( $_SERVER['DOCUMENT_ROOT'] . $target ) ) {
if ( $handle_target = fopen( $_SERVER['DOCUMENT_ROOT'] . $target, "r" ) ) {
flock( $handle_target, LOCK_SH );
$data_target = fread( $handle_target, filesize( $_SERVER['DOCUMENT_ROOT'] . $target ) );
flock( $handle_target, LOCK_UN );
fclose( $handle_target );
}
// virtual_directoryを取得
if ( preg_match( "/<link rel=\"up\" href=\"(.*?)\"\/>/", $data_target, $regs ) ) {
$vdir_target = $regs[1];
}
// タイトルを取得。
if ( preg_match( "/<title.*?>(.*?)<\/title>/is", $data_target, $regs ) ) {
$title_target = $regs[1];
}
// ファイルがなかったら現在のパスからゲットする。
} else {
// 分割して最後の一個はタイトルに、再結合してvdirに
$target_exp = explode( '/', $target );
array_pop( $target_exp );
$title_target = array_pop( $target_exp );
$vdir_target = implode( '/', $target_exp );
}
// ループが0回目の時は持ち込まれたものを使う
if( $loop == 0 && $title_first != "" ) {
$title_target = $title_first;
}
// hoge - は削除
if( preg_match( "/^.*? - (.*)$/", $title_target, $tit ) ) {
$title_target = $tit[1];
}
// 親取得ルーチン
// vdir_target が空だったら、一個上かそのディレクトリのindex.xml
if( $vdir_target == "" ) {
// index.xml以外だったらそのディレクトリを取得する
if( basename( $target, '.xml' ) != 'index' ) {
$dir_oya = dirname( $target );
} else {
// 現在のディレクトリを分割
$temp_oya = explode( "/", $target );
// 親ディレクトリは一個上
// よってファイル名とディレクトリ名で2個減らしてくっつけ直す
array_pop( $temp_oya );
array_pop( $temp_oya );
// implodeするとき、/だけだと消えるので
if( count( $temp_oya ) == 1 ){
$dir_oya = '/';
} else {
$dir_oya = implode( "/", $temp_oya );
}
}
// 値があった場合はそれを使用
} else {
$dir_oya = $vdir_target;
}
// dir_oya が有ると言うことはまだ上があるってことだ
if ( $dir_oya ) {
$oya = get_vd( $dir_oya, NULL, $loop + 1 );
foreach( $oya as $value ) {
$result[ $value['depth'] ] = $value;
}
}
// 値に現在の値を代入
// roopが0の時はその場所なのでリンクは不要
// ファイルがないときもリンクは無し
if( $loop != 0 && file_exists( $_SERVER['DOCUMENT_ROOT'] . $target ) ){
if( basename( $target, '.xml' ) == 'index' ) {
$target = preg_replace( "/^(.*\/).+?$/i", '$1', $target );
}
$result[ $loop ] = array(
'path' => $target,
'subject' => $title_target,
'depth' => $loop
);
} else {
$result[ $loop ] = array(
'path' => '',
'subject' => $title_target,
'depth' => $loop
);
}
return $result;
}
少し長めで読みにくいですが行っていることは以下の通りです。
title
要素内の値を取得する。rel
属性が「up」のlink
要素が有る場合はそのパスを取得しておく。link
要素が有った場合はそのパスを上位ファイルとして扱い、無かった場合は単純に1つ上のディレクトリ(index.xml)を上位ファイルとして扱う。たとえばget_vd( "/works/fb2k/install/" )
とすると
Array ( [3] => Array ( [path] => / [subject] => Arielworks.net [depth] => 3 ) [2] => Array ( [path] => /works/ [subject] => Works [depth] => 2 ) [1] => Array ( [path] => /works/fb2k/ [subject] => foobar2000 [depth] => 1 ) [0] => Array ( [path] => [subject] => インストールと日本語化 [depth] => 0 ) )
このような配列が帰ってきます。
文章としての独立性を持たせるため「foobar2000 - インストールと日本語化」というタイトルがXML文章には与えられていますが、このままだと上位ディレクトリの「foobar2000」と被ってしまうので途中で正規表現を使って「foobar2000 - 」の部分をカットしています。
呼び出すときに「/works/fb2k/install/foo.xml」等、ディレクトリ以外を引数にした場合、「foo.xml」は「/works/fb2k/install/」の構成ファイルの1つと見なし、上位ディレクトリは「/works/fb2k/」ではなく、同一ディレクトリの「/works/fb2k/install/」になります。1ページ1ディレクトリ方式に関しては『XMLベースのウェブサイトを構築する(1)』内で説明しています。
この関数自体は完全にこのサイト用にカスタマイズされているので他の環境で使用する場合は取得するファイルを「index.xml」ではなくて「index.php」などにする必要があるでしょう。
活用法
このサイトでは得られた配列$vd
を以下のようにしてリストにしています。
$temp_hp = "<ol class=\"html_path\">\n";
foreach( $vd as $value ) {
if ( $value['path'] ) {
if( $value['subject'] == 'Arielworks.net' ) {
$temp_hp .= '<li class="arielworks"><a href="' . $value['path'] . '" accesskey="'. ( count( $vdir ) - $value['depth'] ) . '">' . $value['subject'] . "</a></li>\n";
} else {
$temp_hp .= '<li><a href="' . $value['path'] . '" accesskey="'. ( count( $vdir ) - $value['depth'] ) .'">' . $value['subject'] . "</a></li>\n";
}
} else {
if( $value['subject'] == 'Arielworks.net' ) {
$temp_hp .= "<li class=\"arielworks\">" . $value['subject'] . "</li>\n";
} else {
$temp_hp .= "<li>" . $value['subject'] . "</li>\n";
}
}
}
$temp_hp .= "</ol>";
$html_path = $temp_hp;
get_vd()
を実行した際、ファイルが存在しなかった場合はパスが空になってるはずなのでその時だけリンクしないようにしています。ルートディレクトリの「Arielwors.net」だけフォントを変えるために条件分岐していますが、これは他の環境では必要ないでしょう。アクセスキーは階層にあわせて1から順に振っておきます。
この後CSSで適当に整形します。Mozillaだけなら
ol.html_path li {
display: inline;
}
ol.html_path li:first-child:before {
content: "";
}
ol.html_path li:before {
content: " -> ";
}
で問題有りませんがIE6が対応していないので
ol.html_path li {
padding-left: 20px !i;
margin-left: 3px !i;
background-image: url( "./arrow" ) !i;
background-repeat: no-repeat !i;
background-position: 0% 50% !i;
}
とIE6のバグを使って適当に回避しておきます。背景画像に矢印の画像を指定して右側に表示される様にしておけば良いわけです。