ナビゲーション

ややこし更新システム

一度まとめておかないと自分でもわけがわからなくなるので、2004-01-18 現在の朝顔日記の更新の仕組みについて書いておきます。

現在、このシステムは使っていません。

日記の構成

直近のもの数日分を掲載した最新版と、月別(時期によっては半月でまとめてある)にまとめたいわゆる過去ログがあります。

最新版では、いちばん新しい記事がファイルの先頭に、過去ログのファイルは逆に時系列で古いものがファイル先頭になるように並んでいます。これは、読み返した場合にその方が読みやすいと思われるからです。

なお最新版は、ローカルで最新版を作成し、それをサーバにアップロードするタイミングでRSS (RDF Site Summary)も生成して、同時にアップロードしています。

RSS をどうやって作っているか

The WEB KANZAKIRSS生成スクリプトのサンプルで配布しているスクリプトを多少いじった以下のスクリプトを使って RSS を生成しています。

#!/usr/local/bin/perl
use Jcode;
$item_title = "h3";     #タイトルとリンクを抜き出す要素
$item_descr = "p";     #説明文を抜き出す要素
$host = "http://diary.noasobi.net/";
#ソースからタイトル、リンク、説明文を抽出
while(<>){
  if(m|<${item_title}.*?><a href="(.*?)">(.*?)</a>|){
    $link[++$items] = $1;
    $items_list .= qq(    <rdf:li rdf:resource="$host$1"/>\n);
    $title[$items] = $2;
    $isItem = 1;
  }elsif(m|<${item_descr}.*?>(.*?)</${item_descr}>| and $isItem){
    $str = $1;
    $str =~ s/<.*?>//g; #不要なマークアップを削除
    $descr[$items] = $str;
    $isItem = 0;        #説明文は1要素だけにしておく
  }
}
#以下、RSSの構文に従って出力
$source .=<<EOF;
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="rss2html.xsl"?>
<rdf:RDF 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xml:lang="ja">
 <channel rdf:about="http://diary.noasobi.net/rss.rdf">
  <title>朝顔日記</title>
  <link>http://diary.noasobi.net/</link>
  <description>のりの日々のあれこれを綴った朝顔日記の最近の記事の一覧</description>
  <image rdf:resource="http://diary.noasobi.net/img/asagao-p.png"/>
  <items>
   <rdf:Seq>
$items_list
   </rdf:Seq>
  </items>
 </channel>
EOF
for $i (1..$#link){
  $source .=<<EOF;
 <item rdf:about="$host$link[$i]">
  <title>$title[$i]</title>
  <link>$host$link[$i]</link>
  <description>$descr[$i]</description>
 </item>
EOF
}
$source .= "</rdf:RDF>\n";
Jcode::convert(\$source,"utf8");
print $source;

RSS を utf-8 で出力させるために Jcode.pm を使用しています。使用法はコマンドラインで以下のコマンドを実行。

Perl c:\web\diary\rss\rss.pl c:\web\diary\latest.html > c:\web\diary\rss.rdf

見出し一覧をどうやって作っているか

月別のファイルからその月の記事の見出し一覧を生成して、最新版をアップするタイミングで、いっしょに見出し一覧も更新していますが、これには以下の perl スクリプト(heading.pl)を用いています。

#!perl
#見出し行を抽出して加工

print "<ul>\n";
while (<>) {
  @headings = /<h3 .+>/g;
  foreach $heading (@headings){
    $heading =~ s/<h3 ([^>]+)><a href="(\w+)\.html#diary_(\d\d)(\d\d)(\d\d)([abcdef])">([^<]+)<\/a><\/h3>/<li $1>($3年$4月$5日) <a href=\"$2.html#diary_$3$4$5$6\">$7<\/a><\/li>/g;

    print "$heading\n";
  }
}
print "</ul>\n";

これを、コマンドラインで次のように実行。

Perl heading.pl 0401.html > midasi\m0401

これにより、htmlのリストの断片が得られますが、これを見出し一覧ファイルでSSI しています。

最新版の日記を生成する仕組み

私自身は、日記は月別のファイルに書いています。この月別のファイルは xhtml1.1 で valid になるように書いているので、xml として扱うことが可能になっています。月別のファイルに xslt をかますことで、最新の日記を生成しています。ただ、月をまたいだ場合でも、最新版に一定数の記事を載せたいので、当月の日記ファイルだけではうまくない場合があるので、前月分の日記と当月分の日記をやはり xslt で連結して、このファイルから最新版を生成するようにしています。以下、前月分と当月分を連結する xslt(renketu.xsl)。これは、今月(2004年1月)用です。


<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:x="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="x" >
<xsl:output method="xml" version="1.0" encoding="Shift_JIS"
indent="yes"
omit-xml-declaration="no"/>
<xsl:template match="/">
<html>
<head>
<title>先月分と今月分</title>
</head>
<body>
<xsl:copy-of select="x:html/x:body/x:div/x:div" />
<xsl:copy-of select="document('0401.html')/x:html/x:body/x:div/x:div" />
</body>
</html>
</xsl:template>
</xsl:stylesheet>

この xslt は、前月分(この場合2003年12月分の0312.html)のファイルに当月分(この場合、2004年1月分の0401.html)のファイルを連結するように出来ているので、以下のようにして実行します。

Msxsl -xe 0312.html renketu.xsl > renketu.htm

xslt変換に使用しているプロセッサは msxsl.exe ですが、コマンドラインオプション、-xeを用いているのは、xhtml1.1を msxsl.exe に読ませると処理できない問題を回避するためと dtd 読まないので処理が早いから。

次に、これを逆順にソートするために以下の sort.xsl を使います。

<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:x="http://www.w3.org/1999/xhtml"
 exclude-result-prefixes="x" >
 <xsl:output method="xml" version="1.0" encoding="Shift_JIS"
  indent="yes"
  omit-xml-declaration="no"/>
 <xsl:template match="/">
  <html>
   <head>
    <title>日付でソート</title>
   </head>
   <body>
    <xsl:apply-templates/>
   </body>
  </html>
 </xsl:template>
 <xsl:template match="x:html/x:body">
  <xsl:for-each select="x:div">
   <xsl:sort select="@id" order="descending"/>
   <div class="section" id="{@id}">
    <h2><a href="/{x:h2/x:a/@href}"><xsl:value-of select="x:h2/x:a"/></a></h2>
    <xsl:apply-templates select="."/>
   </div>
  </xsl:for-each>
 </xsl:template>
 <xsl:template match="x:html/x:body/x:div">
  <xsl:for-each select="x:div">
   <xsl:sort select="@id" order="descending"/>
   <xsl:copy-of select="."/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

これにより、逆順にソートされた前月分と当月分を連結したファイルが得られますが、そのままでは多すぎるので、適当な数だけ切り出して、最新版の体裁を与えるのが、latest.xsl です。


<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:x="http://www.w3.org/1999/xhtml"
 exclude-result-prefixes="x" >
 <xsl:output method="xml" version="1.0" encoding="Shift_JIS"
  doctype-public="-//W3C//DTD XHTML 1.1//EN"
  doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
  omit-xml-declaration="no"
  indent="yes"/>
 <xsl:template match="/">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
   <head profile="http://diary.noasobi.net/profile.html">
    <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />
(中略)
    <title>朝顔日記</title>
   </head>
   <body>
(中略)
    <div class="content diary">
     <h1>今年こそは(何)</h1>
     <p id="start">これより本文</p>

     <xsl:variable name="first" select="1"></xsl:variable>
     <xsl:copy-of select="x:html/x:body/x:div[$first]"/>
     <xsl:variable name="second" select="2"></xsl:variable>
     <xsl:copy-of select="x:html/x:body/x:div[$second]"/>
     <xsl:variable name="third" select="3"></xsl:variable>
     <xsl:copy-of select="x:html/x:body/x:div[$third]"/>
     <xsl:variable name="fourth" select="4"></xsl:variable>
     <xsl:copy-of select="x:html/x:body/x:div[$fourth]"/>
    </div>
    <div id="navi">
     <xsl:comment>#include file="navi.txt"</xsl:comment>
    </div>
   </body>
  </html>
 </xsl:template>
</xsl:stylesheet>

結局、前月分から最新版を作るために、以下を実行することになります。仮にrenketu.batと名付けます。

Msxsl -xe 0312.html renketu.xsl > renketu.htm
Msxsl renketu.htm sort.xsl > sort.htm
Msxsl sort.htm latest.xsl > latest.html

FTP はどうなっているのか

このサイトは、おおむね3ヶ所くらいから更新することがあるので、日記を書くときには必ずサーバから当月分のファイルをダウンロードして編集しています。最初は、一般的な ftp ソフトを使っていたのですが、面倒くさいのでダウンするのも、アップするのもバッチファイルで行うことにしました。実際に使う ftp ソフトは、Windows にもれなく付いてくる ftp.exe です。あまり知られていないようですが、ftp.exe は別ファイルに記述したコマンドを順次実行してくれる機能があるのです。以下、日記をダウンロードするためのコマンドを記述したファイル(diary_dn.scr)の中味です。


USER-ID
PASSWORD
cd public_html/hogehoge/diary/
get latest.html
get 0401.html
quit

これは使うのには、コマンドラインで以下のようにします。

FTP -s:c:\web\diary\ftp\diary_dn.scr www.noasobi.net

逆にアップする時のコマンドを記述した diary_up.scr は、以下のような感じです。


USER-ID
PASSWORD
cd public_html/hogehoge/diary/
put latest.html
put 0401.html
put rss.rdf
quit

更新システムの更新

ここまで書いてきたシステムは、先月分のファイルや当月分のファイルなどの具体的なファイル名を用いているので、このままでは、月が変わるたびに各種バッチファイルなどを書き換える必要があります。それでは面倒ですし、間違いの元にもなるのでこれらのファイルも自動で更新できるようにしてあります。

日記を書くとき

日記を書くときは、diary.bat というのを実行しています。以下、diary.bat の中味。


@echo off
cd \
cd c:\web\diary
Perl batmake.pl
FTP -s:c:\web\diary\ftp\diary_dn.scr www.noasobi.net
c:\web\diary\ftp\diaryopen.bat

Perl batmake.pl という行がありますが、これがキモです。以下、batmake.pl というperl スクリプトの中味。

#perl
@date = localtime(time);
$date=sprintf("%02d%02d",$date[5]-100,$date[4]+1);

open(FILE,">ftp/diaryopen.bat");
print FILE "c:\\bin\\xyzzy\\xyzzycli.exe c:\\web\\diary\\$date.html -go 3000\n";
close(FILE);

open(FILE,">ftp/diary_dn.scr");
print FILE "USER-ID\n";
print FILE "PASSWORD\n";
print FILE "cd public_html/hogehoge/diary/\n";
print FILE "get latest.html\n";
print FILE "get $date.html\n";
print FILE "quit\n";
close(FILE);

open(FILE,">ftp/diary_up.scr");
print FILE "USER-ID\n";
print FILE "PASSWORD\n";
print FILE "cd public_html/hogehoge/diary/\n";
print FILE "put latest.html\n";
print FILE "put $date.html\n";
print FILE "put rss.rdf\n";
print FILE "cd midasi\n";
print FILE "put midasi/m$date\n";
print FILE "quit\n";
close(FILE);

open(FILE,">rss.bat");
print FILE "cd \\\n";
print FILE "cd c:\\web\\diary\n";
print FILE "Perl c:\\web\\diary\\rss\\rss.pl c:\\web\\diary\\latest.html > c:\\web\\diary\\rss.rdf\n";
print FILE "Perl heading.pl $date.html > midasi\\m$date\n";
print FILE "FTP -s:c:\\web\\diary\\ftp\\diary_up.scr www.noasobi.net\n";
close(FILE);

diaryopen.bat というのは、当月分の日記ファイルをエディタで開くバッチファイルです。diary.bat を実行することで、ftp 用のコマンドを記述したファイルと、RSS を作成するためのバッチファイルが生成され、その後、サーバから当月分と最新版のファイルをダウンロードしてきて、エディタで当月分の末尾(正確には、ファイル先頭から3000行目)が開かれます。私の環境では、バッチファイルを実行して当月分のファイルがエディタで開かれるまで2~3秒くらいで完了します。

日記を書き終わったら

日記を書き終わったらとりあえず、最新版を作ってブラウザでのレンダリングを確認します。latest.bat を実行することでそれを行います。latest.bat の中味は以下のとおり。


cd c:\web\diary
Perl latest.pl
call renketu.bat

途中で使われている、latest.pl の中味は以下のとおり。これを使って、ファイル名に依存する部分を生成しています。


#!perl
@date = localtime(time);
$this_month=sprintf("%02d%02d",$date[5]-100,$date[4]+1);
$prev_month = sprintf("%02d%02d",$date[5]-100-($date[4] == 0),$date[4] + 12*($date[4] == 0));

#renketu.xsl 生成
open(FILE,">renketu.xsl");
print FILE "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n";
print FILE "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n";
print FILE "xmlns=\"http://www.w3.org/1999/xhtml\"\n";
print FILE "xmlns:x=\"http://www.w3.org/1999/xhtml\"\n";
print FILE "exclude-result-prefixes=\"x\" >\n";
print FILE "<xsl:output method=\"xml\" version=\"1.0\" encoding=\"Shift_JIS\"\n";
print FILE "indent=\"yes\"\n";
print FILE "omit-xml-declaration=\"no\"/>\n";
print FILE "<xsl:template match=\"/\">\n";
print FILE "<html>\n";
print FILE "<head>\n";
print FILE "<title>先月分と今月分</title>\n";
print FILE "</head>\n";
print FILE "<body>\n";
print FILE "<xsl:copy-of select=\"x:html/x:body/x:div/x:div\" />\n";
print FILE "<xsl:copy-of select=\"document('$this_month.html')/x:html/x:body/x:div/x:div\" />\n";
print FILE "</body>\n";
print FILE "</html>\n";
print FILE "</xsl:template>\n";
print FILE "</xsl:stylesheet>\n";
close(FILE);

#renketu.bat 生成
open(FILE,">renketu.bat");
print FILE "Msxsl -xe $prev_month.html renketu.xsl > renketu.htm\n";
print FILE "Msxsl renketu.htm sort.xsl > sort.htm\n";
print FILE "Msxsl sort.htm latest.xsl > latest.html\n";
close(FILE);

出来上がった最新版の日記や当月分の日記にいろいろ不備がないこと確認(文法チェックーにも毎回かけています)したのち、次に実際にサーバにアップします。rss.bat を実行することでそれを行いますが、rss.bat は、diary.bat を実行して時点で生成されています。

rss.bat を実行することで html などはサーバにアップできますが、日記に使った画像ファイルなど html 以外のものはアップされないので、これは別途普通(?)に ftp しています。ここらあたりも自動化できるといいのですが、そうしょっちゅう画像ファイルをアップすることもないので、当面は手動でもさほど面倒は感じていません。RSSを生成して、htmlファイルや見出し一覧アップにかかる時間は、やはり2~3秒というところでしょうか。

サーバでの作業

おおむね、以上でややこしシステムは完成したかに思えたのですが、実は月初めに落とし穴がありました。日記を書くときにサーバから当月分をダウンしてから日記を書くようになっているわけですが、月初めにはまだそのファイルがないわけです。

ということで、月が変わったら自動で新しい月のファイルを生成するようにしました。ローカルで作ってアップするよりもサーバで生成させた方が簡単なので、以下の perl スクリプトを cron を使って月に一度実行するようにしてあります。


#!/usr/bin/perl
@date = localtime(time);
$year=sprintf("%04d",$date[5]+1900);
$this_month=sprintf("%02d",$date[4]+1);
$prev_diary = sprintf("%02d%02d",$date[5]-100-($date[4] == 0),$date[4] + 12*($date[4] == 0));
$this_diary = sprintf("%02d%02d.html",$date[5]-100,$this_month);
if (-f "/home/hogehoge/diary/$this_diary"){
  exit;
}
open(FILE,"+>/home/hogehoge/diary/$this_diary");
print FILE <<__HTML__;
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
(中略)
</body>
</html>

__HTML__
close(FILE);
chmod(0754, "/home/hogehoge/diary/$this_diary");

最後の行で、ファイルに実行属性を与えているのは、XBitHack full で SSI を有効にするためです。

実際に私が実行するのは

長々と書いてきて、だいぶややこしく面倒なように見えますが、実際に私が行うコマンドの実行はわずかです。日記を書くときは、diary.bat を実行。書き終わったら、latest.bat で、最新版をローカルで確認。rss.bat で関係ファイルをアップという手筈になっています。

実際に日記を書くときは、他にも xyzzy の各種 lisp などをいろいろ使っていたりするわけですが、これは単に html の記述を楽に行うためのものでして、日記の更新システムとはあまり関係がありません。ま、その辺もぼちぼちまとめたいところではありますが。

「ややこし更新システム」へコメントをつける

書き込み時の挙動について
URI らしき文字列には自動的にリンクが張られます。また "<" や、">" は実体参照化されます。
スパム対策のため、リファラを切っていると投稿できません。

この記事の永続的 URI ならびに トラックバック ping URI
http://diary.noasobi.net/etc/yayakosi.html