レベル: 初級 Benoit Marchal (bmarchal@pineapplesoft.com), Consultant, Pineapplesoft
2001年 8月 01日 実用的なXML の今回の記事で、Benoit MarchalはXMLフィルターを使用して、彼が開発した新しい機能である、オープン・ソースのWebパブリッシング・アプリケーションをXMに追加します。2つの新しいフィーチャーのおかげで、XMは簡単なWebサイトを扱えるほど強力になりました。フィルター操作およびその他の技法の使用、およびXMコードの更新を示すコード・サンプルが提供されています。また、アプリケーションのソース・コードをダウンロードするためのリンクも用意されています。
先月から始まった実用的なXMLコラムは、Benoît MarchalのXMLテクノロジーに基づくオープン・ソース・アプリケーションの進化をたどるものです。Benoîtは、コンテンツ・マネージメントおよびWebパブリッシング・ソリューションであるXM (XSLT Make) を始めとするアプリケーションを開発しながら、彼のデザイン決定、技法、コード、および彼が学んだ教訓を読者と共用します。
実用的なXML コラムの第1回にあたる先月の記事で、私はXMLおよびXSLTに基づくコンテンツ・マネージメントおよびWebサイト公開のための取り組みやすいソリューション、XMを紹介しました。ここで重要なのは、取り組みやすい ということです。ハイエンド・サイト向けには、商用の優れたコンテンツ・マネージメント・ソリューションが存在しますが、それらは多くのWebマスターにとっては値が張りすぎます。逆に、自作のソリューションは安上がりですが、開発者には向いているものの、あまり技術知識のないエンド・ユーザーには便利ではありません。
(先月紹介した) XMの最初のバージョンは、限定されたものでした。これは一連のディレクトリーを再帰的にたどりながらXSLTスタイル・シートを適用するものです。これには、多くの重要なフィーチャーが欠けています。今月は、2つの最も緊急な対応を要する制限に対処します。その結果として、簡単なWebサイトのために使用できるソリューションが得られますが、今後数か月のうちにさらに改善できる余地があります。(記録のために、XMを使用して、参考文献で示したananas.org Webサイトを公開しました。)
リンク・マネージメント
今月の2つの新規フィーチャーは、リンク・マネージメントと、非XMLファイル処理機能です。2つのフィーチャーは、関連し合う部分があります。では、リンク・マネージメントから始めましょう。
リンクの修正
ほとんどの場合、XMはスタイル・シートの適用時にいくつかのファイルの名前を変更しますので、リンクを切断してしまいます。図1 はその様子を示しています。Webマスターが、1つのハイパーリンクを備えた2つのファイル (index.xmlとdetails.xml) を所有しているものとします。XMはこれらのファイルの名前を、それぞれindex.htmlおよびdetails.htmlに変更しますので、リンクは切断されます。
理論上は、スタイル・シートでリンクを修正することができます。実際、.xmlを .htmlで置き換えるだけで十分です。しかし、新規フィーチャーをXMに追加しようと考えていますので、スタイル・シートでリンクを修正するのは、もっと難しくなるはずです。XMにリンクを自動的に修正させるほうが理にかなっています。
図1. 結果ファイルの拡張子が変化するため、XMによってリンクが切断されます。
リンクの切断を防ぐ方法
リンク・マネージメントでは、リンクの切断を防止し、また検出する必要があります。ユーザーは「404 - File Not Found」エラーを嫌悪していますので、XMではそうしたエラーを減らさなければなりません。ハイエンドのコンテンツ・マネージメント・ソリューションはデータベースと連携し、各文書に固有のIDを割り当てます。これらのIDを使用することにより、挿入や削除をモニターし、無効なリンクを防ぐことができます。XMにはそうしたぜいたくな機能はありませんので、リンクがファイルを指しているのかどうかをテストするだけにとどめています。リンクがファイルを指していない場合、XMLはエラーを報告します。
利便性を考えて、相対パスと絶対パスの長所を組み合わせた簡単なメカニズムを工夫しました。私はこれを「相対的な絶対パス」と呼んでいます。相対パスは、ファイル・システムからWebサイトをテストするのに特に便利です。一方、絶対リンクは正確性の高さが期待できます。
絶対パスは、スタイル・シートによって作成されたリンクの場合に特に役立ちます。説明のために、ananas.orgにおけるdeveloperWorksに関するイメージを考えてみます。スタイル・シートは /images/buttons/dw.gifからイメージをロードします。このスタイル・シートが相対リンクを使用しなければならないとしたら、どうなるでしょう?XML文書に関するimages/buttons/dw.gifへのリンクをルートに挿入し、それらの文書に関する ../images/buttons/dw.gifへのリンクを、ルートの1つ下のレベルに挿入することになります。しかし、スタイル・シートは、ルートに文書があることをどのようにして検出するのでしょうか?やはり、スタイル・シートに代わってXMにこれを管理させたほうが簡単です。
相対的な絶対パスがその解決策です。先頭文字を! で置き換えてください。XMリンク・マネージメントがそのリンクを相対パスに変化させます。これは、ファイル・システムからWebサイトをテストする場合にのみ便利です。たとえば、先月のバージョンのXMでは、スタイル・シートで絶対パスを使用する必要があり、サイトをローカルでテストしたときにはイメージが正しく表示されませんでした。もちろん、読者が常にWebサーバーから自分のサイトにアクセスするのであれば、このフィーチャーを使用することはありません。
LinkFilter
リンク・マネージメントはLinkFilter でインプリメントされています。このクラスは、その名前から想像できるとおり、SAXXMLFilterImpl を拡張したXMLフィルターです。XMLフィルターについてまだよく知らない読者は、この先を読んでください。私は、フィルター操作はSAXの最も便利なフィーチャーの1つであると信じています。
簡単に言うと、フィルターとは、受け取ったイベントを別のイベント・ハンドラーに渡すSAXイベント・ハンドラーのことです。ただし、その際にいくつかのイベントを変更 (つまり、フィルター操作) します。フィルターは、いわゆるパイプラインと相互に結び付いている場合には特に便利です。図2 を参照してください。この構成では、文書がパイプを通過しています (補足記事「SAXイベントのロギング」を参照してください)。各ステップでフィルターが文書を変更します。パイプは、入力文書を少しずつ出力文書に変えてゆきます。
フィルターは、XML変換を複数の別個のステップに分断するための簡潔なひな形となります。フィルターは簡単に新しい構成に再結合できるため、非常に便利です。また、受け取ったイベントをすぐに処理するため、効率も高くなっています。
図2. フィルターのパイプライン
SAXイベントのロギング
パイプを構築するときには、どのイベントがパイプを通過するのかを知っておくと便利です。私は、このパイプをテストするためにContentHandlerExtractor クラスを作成しました。このクラスは、できるかぎり多くの情報をファイルにダンプします。あとでこのファイルを分析することにより、どのイベントがいつ実行されるのかをよく理解することができます。このクラスのためのコードは、org.ananas.util パッケージ内のソース・リポジトリーに収められています (参考文献を参照してください)。
リンク・マネージメントの課題の1つは、XML文書内のURIを認識することです。XMLの語彙は非常に多いため、どれをサポートするのかを決めるのは困難です。私は最初、XMLの入力文書をプリプロセスしようとしました。そうすれば、XSLTプロセッサーに到達したときにはリンクが正しくなっているはずだと考えたのです。私はXMLの標準リンク語彙であるXLink、ananas.orgのために私が使用している語彙であるDocBook、およびNewsMLなどをサポートしようと考えました。
やがて私は、文書をポストプロセスしたほうがよいであろうと決断しました。つまり、スタイル・シートを使用して実行したあとで、そのリンクを修正することにしました。この方法は、Webサイトを公開するために使用する語彙が比較的少ないため、大変魅力的です。つまり、HTML、RSS、および (まもなくPDFパブリッシング用に使用されるようになる) XSL FOだけで済みます。現行バージョンはHTMLだけに制限されています。XMがWebマスターを対象としていることを考えれば、妥当な選択でしょう。
リスト1. LinkFilter.java は、リンク・マネージメントをインプリメントしています。XMLFilterImpl は、すでにほとんどのContentHandler をインプリメントしています。これは、パイプ内の次のフィルターにイベントを手当たり次第に転送するものです。リンクを修正するために私が行う必要のあることは、startElement() をインターセプトすることだけです。この論理は次のようになっています。
- URIを含む属性を抽出する。
-
! を適切な相対パスで置き換えて、いわば絶対的な相対パスを処理する。
- ファイルがローカル・ファイルの場合には、ファイル名をXMが使用する名前で置き換えて、URIを修正する。
MoversSupervisor については、すぐあとで検討します。
- ファイルがローカル・ファイルでなく、外部URIのように見えない場合には、エラーを報告する。「外部URIのように見えない」とはどういうことでしょうか?基本的には、ファイル名が
http:、ftp:、news:、またはmailto: で始まらない、ということです。理論的には、リモート・サーバーに接続して4xxまたは5xx応答コードが戻されるかどうかをテストするのは簡単です。しかし実際には、そうするとXMの速度が著しく低下するため、この方法は取らないことにしました。
LinkFilter は、明らかに、ファイル・システムについて1つまたは2つのことを知っている必要があります。この情報はsetDirectory() メソッドによって提供されます。
読者は、comment()、endCDATA()、および類似のメソッドが何をするのかについて、興味を持っているかもしれません。これらのメソッドはLexicalHandler インターフェースをインプリメントしています。LexicalHandler はSAX2におけるオプションのイベント・ハンドラーであり、コメント、CDATAセクション、エンティティーその他の特別な字句構成を扱います。XSLTプロセッサーはLexicalHandler を使用してそれらを戻します。
LexicalHandler はオプション・インターフェースですので、XMLFilterImpl にはsetLexicalHandler() メソッドはありません。その代わりに、汎用のsetProperty() メソッドが使用されます。字句ハンドラーは、あまり多くのことを行いません。イベントをパイプ内の次のフィルターに転送するだけですが、XSLTプロセッサーにはそれらのイベントが必要です。
千両役者Mover
XMに対して今月行われた改良の2つ目は、非XMLファイルを扱う機能です。先月からのDirectoryWalker には// else copy file というコメント行があります。今月は、else を書くことにします。
今後数か月の予定として、ディレクトリーのための特別なプロセッサーを組み込むことを考えています。そうしたプロセッサーを使用して、XMは公開時にXMLファイルを動的に作成することになります。XMはまもなく、いくつかのファイルのカテゴリーを認識することになると思います。これには、XMLはもちろん、ディレクトリー・プロセッサー・タイプ、その他のタイプが含まれます。そこで、ファイル処理の要約を後回しにしないで、ここで行っておくことにします。役に立つときがすぐに来るからです。
Mover
私は古典的なパターンに従って操作を要約し、Mover インターフェースを導入しました。その名前が示すように、Mover はファイルをソースから公開用ディレクトリーに再配置します。その際、このインターフェースはスタイル・シートを適用し、(拡張子を .xmlから .htmlに変更して) ファイルを名前変更することがあります。私は古典的なパターンに従っていますので、このコーディングはほとんど頭を使わずに行えます。最も難しい部分は、インターフェースのためによい名前を決めることでした。
このインターフェースはリスト2. Mover.java で定義されています。これは、次の2つのメソッドを処理します。
-
move()。これは、読者が予想するとおり、ソースからターゲット・ディレクトリーにファイルをコピーします。
-
getTargetName()。これは、このムーバーがファイルを移動する必要がある場合、ムーバーが使用する名前を戻します。このメソッドは、すでに見たように、リンク (より具体的に言えば、ファイル接尾部) を修正するためにLinkFilter によって使用されます。
リスト2. Mover.java
package org.ananas.xm;
import java.io.*;
public interface Mover
{
public File move(File sourceFile,File targetDir,int depth)
throws IOException, XMException;
public String getTargetName(File file)
throws XMException;
}
|
コーディングを単純化するために、抽象クラスDefaultMoverImpl を用意しました。これはソース・リポジトリーから入手することができます (参考文献を参照してください)。このクラスはMover インプリメンターのために、いくつかの便利なメソッドを提供します。インターフェースと抽象クラスの両方を提供することは無駄なようですが、便利な点があります。ほとんどの場合、必要とはなるのは抽象クラスだけですが、インターフェースがあると複数の継承が可能になります。
StylingMover
Mover の最初の具体的なインプリメンテーションはリスト3. StylingMover.java です。これは、ファイルをターゲット・ディレクトリーにコピーするときにXSLTスタイル・シートを適用します。このコードは、先月のDirectoryWalker からほとんどそのまま引き継いだものです。違いは、LinkFilter の使用に関するものです。
結果をファイルに保管するには、どうしたらよいのでしょう?パイプはSAXイベントを通過させますが、必要なのはイベントではなくファイルなのです。パイプ内の最後の要素は、イベントをXMLまたはHTMLファイルに書き込まなければなりません。実際には、LinkFilter のあとで、パイプを終了させるための特別なContentHandler が必要です。最も簡単な解決策は、Xalan提供のシリアライザーを使用することです。このシリアライザーはSAXイベントを受け入れ、対応するXML文書をファイルに書き込みます。Xalanシリアライザーは、HTML文書を保管するためにXalanが内部的に使用するクラスですので、使用コストがかかりません。StylingMover は、このシリアライザーを含め、パイプを構築します。
CopyingMover
2つ目のMover はリスト4. CopyingMover.java にあります。これは、ファイルが変更された場合、そのファイルをターゲット・ディレクトリーにコピーします。効率を高めるために、小さなバッファーを使用しています。StylingMover とは異なり、CopyingMover は、ファイル名を変更することはありません。
リスト4. CopyingMover
package org.ananas.xm;
import java.io.*;
public class CopyingMover
extends DefaultMoverImpl
{
protected byte[] bytes = new byte[1024];
public String getTargetName(File file)
{
return file.getName();
}
public CopyingMover(Messenger messenger)
{
super(messenger);
}
public File move(File sourceFile,File targetDir,int depth)
throws XMException
{
try
{
File targetFile = new File(targetDir,getTargetName(sourceFile));
if(targetFile.exists())
{
if(targetFile.lastModified() >= sourceFile.lastModified())
return null;
}
synchronized(bytes)
{
FileInputStream in = new FileInputStream(sourceFile);
FileOutputStream out = new FileOutputStream(targetFile);
for(int len = in.read(bytes);len != -1;len = in.read(bytes))
out.write(bytes,0,len);
}
return targetFile;
}
catch(IOException e)
{
messenger.error(new XMException(e));
}
return null;
}
}
|
MoversSupervisor
リスト5. MoversSupervisor.java では、ファイルに適したムーバーを選択します。これは、ファイルの接尾部だけを調べています。.xmlファイルは
StylingMover に送られ、それ以外のすべてのファイルはCopyingMover に送られます。
MoversSupervisor も、すべてのムーバーのコピーを作成し、保存します。相対的に制限された現行バージョンのXMを考えると、このクラス (およびすべての
Mover シリーズ) は明らかに大げさですが、来月になると、これが様々なことの単純化に役立つようになります。
DirectoryWalker
大事なことを忘れるところでした。新規バージョンのXMで、私は、ムーバーを使用するようにDirectoryWalker を更新しなければなりませんでした。これにより、リスト6. 帰ってきたDirectoryWalker に示すように、ディレクトリーを調べる論理が、ファイルをソースからターゲット・ディレクトリーに移動させる論理と明確に分離されます。
その他の更新
もう1つ、報告しておかなければならない変更内容があります。Messager (およびその仲間のDefaultMessager) の名前を、Messenger (およびDefaultMessenger) に変更しました。タイプ・ミスをしてしまい、申し訳ありませんでした。
先月、何人かの読者がソース・コードのダウンロードで苦労したようです。これは、第1回が公開されてまもなく、developerWorksがOpen Source zoneを新しいプラットフォームに移動したという、不運な事情によるものです。
developerWorksが新しいシステムをオンラインにすることに伴う問題がこれ以上起こらないようにするために、ZIPファイルを使用できるようにする予定です (参考文献を参照してください)。これは一時的な対策ですので、CVSリポジトリーが使用可能な場合には、そちらのほうにアクセスするようにしてください。ただし、このリポジトリーが一時的に使用できなくなっている場合には、ZIPファイルをダウンロードする必要があると思われます。
ところで、ファイルのアクセスに問題が生じた場合には、ananasディスカッション・メーリング・リストでご連絡ください (参考文献を参照してください)。
読者の番です
XMは形が整いつつあります。先月リリースしたコードはあまり役に立つものではありませんでしたが (これは、XMでananas.orgを公開するのに忙しかったせいでもあります)、今月のバージョンはそれよりも実用的になっています。まだ重要なフィーチャーがそろっていませんが、簡単なWebサイトには十分なはずです。
今度は読者の番です。XMをダウンロードし、お気付きの点をananasディスカッション・メーリング・リストでお知らせください。さらに手直しが必要な個所を見つけるために、協力をお願いします。近い将来の予定として、いくつかのXSLTファイルをサポートし、ディレクトリーを生成するつもりです。
参考文献
- このプロジェクトに関するコードは、ananas.org からダウンロードすることができます。developerWorksでCVS repository までリンクをたどるほか、ananasディスカッション・メーリング・リストを利用してください。このメーリング・リストに参加して、読者の意見をプロジェクトに反映させてください。
- CVSリポジトリーで問題が生じた場合には、代わりにこちらのZIPファイルにアクセスしてみてください。
- XMは、XSLTプロセッサーとしてXalan を、またXMLパーサーとしてXerces-J を使用しています。Xalanは、もともとIBMの子会社Lotusによって開発されたもので、XercesはIBMによって開発されたものです。IBMはこれらのコードをApache Foundationに寄贈しています。
-
Lotus DominoもXMLをサポートします。Dominoは大量の情報を管理するための優れたソリューションです。
著者について  | 
|  | Benoit Marchal氏は、ベルギーのナミュールを拠点にしたコンサルタントおよび著述家です。彼の著作には、 XML by Example(Que社、邦訳: インプレス社「実例で学ぶXML」。間もなく第2版が出版される予定です)、 Applied XML Solutions および XML and the Enterprise があります。また、Gamelanのコラムや、developerWorks XML zoneのコラムWorking XML の著者でもあります。最新プロジェクトの詳細については、www.marchal.com をご覧ください。 |
記事の評価
|