レベル: 初級 Benoit Marchal (bmarchal@pineapplesoft.com), Consultant, Pineapplesoft
2002年 8月 01日
ユーザー・インターフェースの開発では、単純化が鍵となります。オプションとコントロールが少ないほど、紛らわしさが減り、エラーの可能性も減ります。Benoitは、XIに関する前回のコラムで、この控えめが肝心 という概念を使用して、テキストとXMLの間の変換ツールであるXIを使いやすく、快適なものにするためのユーザー・インターフェースを作成しました。
XI (これは、「XML Import (XMLインポート)」を表しています) は、テキスト・ファイルをXMLに変換するためのツールです。多くのアプリケーションによってテキスト・ファイルが作成されていますので、このようなツールを使用することにより、大量の有益なデータをXMLワークフローにインポートすることができます。そもそも私がXIを設計したのは、XMLで住所録を検索するためでした。しかし、XIは入力ファイルの構文解析に正規表現 (JDK 1.4で導入されたものです) を使用するため、サーバー・ログ、コンマで区切られた値 (CSV)、Excelファイル、およびその他多くの文書でも、同じように役立てることができます。XIは、レガシー・データを最新のXMLフローにインポートするための汎用ツールキットと考えるべきです。
正規表現とXSLTの結合
前回のコラム「XIのまとめ」の中で、私がXMLReader インターフェースの実装を選択したことを覚えておられることと思います。XIパーサーからXMLを直接作成するのではなく、ソフトウェアにXMLを生成させるのです。多くの点で、XIはXMLパーサーのように振る舞いますが、どのような テキスト文書でも構文解析する点でXMLパーサーと異なっています。
リスト1 (これはXIシリーズの最初のコラムでも示しました) は住所録です。これは、Eメール・アプリケーションによってテキスト文書として維持されています。
リスト1. オリジナルの住所録
alias "jdoe" jdoe@xmli.com
note "jdoe"
<country:US><zip:45202>
<state:OH><city:Cincinnati>
<address:34 Fountain Square Plaza>
<name:John Doe>
alias "jsmith" jsmith@worth-it.com
note "jsmith"
<first:Jack><last:Smith>
<name:Jack Smith>
alias "pdupont" pdupont@pineapples.net
note "pdupont" <name:Pierre Dupont>
|
2段階式の変換
XIの前提となっているのは、テキスト文書をXMLに変換するには、次の2段階で行うのが最もよいという考え方です。
- XIパーサーが文書をおおまかなXMLバージョンにする。このおおまかなXMLは、構文的には正しいものですが、適切なボキャブラリーが使用されていない可能性があります。
- このおおまかなXML文書を、1つまたは複数のXSLTスタイル・シートによって後処理する。経験上、クリーンなXML文書を作成するためには、十分な機能を備えたスクリプト言語が必要になることが多いことが分かっていますが、XSLTはそうした目的のために使用可能な、最適な言語の1つです。
住所録をXMLに変換するためには、データの一部を再編成する必要があります。例えば、テキスト文書では、Eメール・アドレスと実際の住所が異なる行に書かれています。私は、XMLではすべてを1つのタグでまとめたいと考えています。また、必ずしもすべての項目が同じデータ・タイプになっているわけではありません (例えば実際の住所など)。最後に、テキスト文書には、XMLの場合には必要のないフィールド (名前、姓) が含まれています。以上は、単純なケースで必要となる再編成作業です。フィールドの値を計算させるような場合には、もっと手の込んだ作業が必要になる可能性があります。最近行ったお客様のプロジェクト (概念的にXIと似たツールを使用するものです) では、いくつかの行にあるデータを合計し、その平均値を求める必要がありました。スクリプト言語は大変便利なものです。
XSLTのリンク
私は、前回のコラムの終わりで、XIReader を受け入れてそれをJavaTransformer に渡し、結果を保管するコードを組み込みました。このサンプルでは、初期化されていないTransformer を使用して構文解析の結果を保管しました。実際には、これによって実装されるのは変換の最初の段階だけです。ただし、XSLTスタイル・シートを使用してTransformer を初期化し、2番目の段階を実行するのは簡単なことです。リスト2 はXSLTLink クラスを示しています。このクラスは完全な変換をカプセル化しています。
リスト2. XSLTLinkクラス
package org.ananas.xi;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;
public class XSLTLink
{
private String name;
private File stylesheet,
output;
private String suffix;
private XMLReader xiReader;
private Transformer transformer;
public XSLTLink(File stylesheet,File output)
throws IOException, SAXException,
TransformerConfigurationException
{
this.stylesheet = stylesheet;
this.output = output;
name = stylesheet.getName();
int pos = name.lastIndexOf('.');
if(pos != -1)
name = name.substring(0,pos);
reload();
}
public void reload()
throws IOException, SAXException,
TransformerConfigurationException
{
InputSource input =
new InputSource(stylesheet.toURI().toString());
xiReader =
XMLReaderFactory.createXMLReader("org.ananas.xi.XIReader");
xiReader.setProperty(XIReader.RULESETS_URI,input);
TransformerFactory factory = TransformerFactory.newInstance();
transformer =
factory.newTransformer(new StreamSource(stylesheet));
suffix = transformer.getOutputProperty("method");
}
public File applyTo(File source,boolean overwrite)
throws IOException, SAXException, TransformerException
{
if(!source.isFile())
throw new IOException(source.getPath() + " is not a file");
InputSource input = new InputSource(source.toURI().toString());
String name = source.getName();
int pos = name.lastIndexOf('.');
String base = pos != -1 ? name.substring(0,pos + 1)
: name + '.';
File result = new File(output,base + suffix);
int index = 1;
while(result.exists() && !overwrite)
{
result = new File(output,base + index + "." + suffix);
index++;
}
transformer.transform(new SAXSource(xiReader,input),
new StreamResult(result));
return result;
}
public String getDisplayName()
{
return name;
}
}
|
XSLTLink は追加サービスを提供します。その1つとして、XSLTスタイル・シートと正規表現をグループ化して1つのファイルにまとめます。これが簡単に実現できたのは、先見の明に満ちたXSLT標準のおかげです。XSLT標準では、xsl:stylesheet 以下に現れていて、しかも別のネーム・スペースで定義されているエレメントを、処理プログラムが無視するように定めています。
XIの規則 (正規表現) には独自のネーム・スペースがありますので、それらをスタイル・シートにコピーすることが可能です。XI処理プログラムはすでに、文書内からそれらの規則を探すようになっています (規則は、文書のルートに現れている必要はありません)。リスト3 はスタイル・シートのサンプルです。この中にはXI規則も含まれていますので、注意してください。
リスト3. 住所録インポート・スタイル・シート
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://ananas.org/2002/xi/rules"
xmlns:an="http://ananas.org/2002/sample">
<xi:rules version="1.0"
defaultPrefix="an"
targetNamespace="http://ananas.org/2002/sample">
<xi:ruleset name="address-book">
<xi:match name="alias"
pattern="^alias ([^\s]*) (.*)$">
<xi:group name="id"/>
<xi:group name="email"/>
</xi:match>
<xi:match name="note"
pattern='^note ([^\s]*) (.*)$'>
<xi:group name="id"/>
<xi:group name="fields"/>
</xi:match>
<xi:error message="unknown line type"/>
</xi:ruleset>
<xi:ruleset name="fields">
<xi:match name="fields"
pattern="[\s]*<([^<]*)>">
<xi:group name="field"/>
</xi:match>
</xi:ruleset>
<xi:ruleset name="field">
<xi:match name="field"
pattern="([^:]*):(.*)">
<xi:group name="key"/>
<xi:group name="value"/>
</xi:match>
</xi:ruleset>
</xi:rules>
<xsl:output method="xml"/>
<xsl:template match="an:address-book">
<sect1><xsl:apply-templates/></sect1>
</xsl:template>
<xsl:template match="an:alias">
<address>
<xsl:variable name="id" select="an:id"/>
<xsl:for-each select="/an:address-book/an:note[an:id = $id]">
<personname><xsl:value-of
select="an:fields/an:field[an:key='name']/an:value"/>
</personname>
<xsl:if test="an:fields/an:field[an:key='country']/an:value">
<street><xsl:value-of
select="an:fields/an:field[an:key='address']/an:value"/>
</street>
<postcode><xsl:value-of
select="an:fields/an:field[an:key='zip']/an:value"/>
</postcode>
<city><xsl:value-of
select="an:fields/an:field[an:key='city']/an:value"/>
</city>
<state><xsl:value-of
select="an:fields/an:field[an:key='state']/an:value"/>
</state>
<country><xsl:value-of
select="an:fields/an:field[an:key='country']/an:value"/>
</country>
</xsl:if>
</xsl:for-each>
<email><xsl:value-of select="an:email"/></email>
</address>
</xsl:template>
<xsl:template match="an:note"/>
</xsl:stylesheet>
|
XSLTLink には、追加サービスが1つあります。つまり、入力から出力名を計算します。オプションの指定に基づき、新規ファイルによって既存のファイルが上書きされないように、固有名を作成します。その理由は、ユーザー・インターフェースで行うべきことで明らかになります。
デバッグのヒント
私は、この2段階方式の手法には、1つの大きな利点があると考えています。つまり、XSLTが多くの選択肢を備えた標準であるということです。市場には、いくつかのXSLT処理プログラムが出回っています。ある処理プログラムがそこそこの出来栄えにすぎないのであれば、別のものを選択することができます。開発者の助けとなる書籍、エディター、デバッガー、その他がそろっています。しかし、欠点が1つあります。すべてのものがスタイル・シートを経由するため、XIによって何が生成されるのかを知ることができません。出力に不備な点がある場合、原因が正規表現であるのか、スタイル・シートであるのか、あるいはその2つの組み合わせであるのかを突き止めるのが困難です。
幸いなことに、対処策が存在します。入力を逐語的にコピーするスタイル・シートを使用するのです。そのために必要となるのは、リスト4 に示す1つのテンプレートだけです。
リスト4. 入力を逐語的にコピーするスタイル・シートの使用
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://ananas.org/2002/xi/rules">
<xi:rules version="1.0"
defaultPrefix="an"
targetNamespace="http://ananas.org/2002/sample">
<xi:ruleset name="address-book">
<xi:match name="alias"
pattern="^alias ([^\s]*) (.*)$">
<xi:group name="id"/>
<xi:group name="email"/>
</xi:match>
<xi:match name="note"
pattern='^note ([^\s]*) (.*)$'>
<xi:group name="id"/>
<xi:group name="fields"/>
</xi:match>
<xi:error message="unknown line type"/>
</xi:ruleset>
<xi:ruleset name="fields">
<xi:match name="fields"
pattern="[\s]*<([^<]*)>">
<xi:group name="field"/>
</xi:match>
</xi:ruleset>
<xi:ruleset name="field">
<xi:match name="field"
pattern="([^:]*):(.*)">
<xi:group name="key"/>
<xi:group name="value"/>
</xi:match>
</xi:ruleset>
</xi:rules>
<xsl:output method="xml"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
新規プロジェクトを開始するときには、常にこのテンプレートをコピーし、読者の正規表現をデバッグするようにしてください。満足のいく正規表現ができたら、スタイル・シートそのものの書き込みを開始してください。デバッグは2段階で行います。
ユーザー・インターフェース
最後に残ったのは、非常に重要なユーザー・インターフェースです。私は、このコラムにおける最初のプロジェクトXM (XSLT Make) をリリースしたときに、適切なユーザー・インターフェースを組み込みませんでした (コマンド行インターフェースしか含まれていません)。その結果、どうなったでしょう?私のもとには今でも、XMを使用したいがコマンド行では使い勝手が悪いという人たちからEメールが送られてきます。XMのこの部分については、将来のコラムで修正する予定ですが、それまでの間は、このプロジェクトでも同じ轍 (てつ) を踏まないようにしたいと思います。
このコラムについて行われた多くの作業は、XI用のユーザー・インターフェースを提供するためのものでした。そのために必要なコードは非常に多いので、このコラムでは、そのすべてをリストすることはしません。それに、読者もすぐにお気付きになると思いますが、これは分かりやすいAWT (Abstract Window Toolkit) コードです。もちろん、developerWorksのオープン・ソース・セクションからプロジェクト全体をダウンロードすることはできます (参考文献を参照)。
ユーザー・インターフェースの決定
最初に、デスクトップ・インターフェースにするのか、あるいはWebベースのインターフェースにするのか? というような、いくつかの選択肢の中から選択を行う必要がありました。デスクトップ・インターフェースのほうが多くのコントロールのセットを含み、より使いやすくなることが多いのですが、Webベースのインターフェースは、どこからでもアクセスできるようにすることができます。最終的には使いやすさを優先し、デスクトップ・インターフェースを採用することにしました。
次に決めなくてはならないことは、AWTを使用するのかSwingを使用するのか、ということでした。私は、大半のJavaテクノロジー開発者にとっては、コントロールのセットが豊富なSwingのほうが気軽に使用できるということを知っていますが、個人的にはSwingが好きではありません。Swingが最初に発表されたときには熱狂的に歓迎したのですが、出来上がった結果には失望しました。はっきり言って、Swingはあまり上手にWindowsをエミュレートしているとは思えません。使い心地が良くないのです。
私は、IBMが支援しているEclipseプロジェクトと、そのSWTツールキット (参考文献を参照) に興味をそそられました。SWTは、ツリー、ツールバー、テーブルなど、より高機能な、ネイティブ・バージョンのコンポーネントを備えています。しかし、このプロジェクトでSWTを使用するのは大げさなような気がしましたので、通常のAWTを使用することにしました。実際のところ、ボックス内でボタンをデフォルトにする機能があればよいのに、と思う程度で、さしたる不便はありません。
ユーザー・インターフェースが行うべきこと
それよりも重要なことは、どのようなユーザー・インターフェースを作るかということです。私は、ユーザー・インターフェースの設計があまり得意ではありませんので、既存のプロジェクトで使用されているものに手を加えたいと思います。今回、出発点として使用するのは、私の著書XML by Example (参考文献を参照) のために書いた、Xalanを包含する単純なラッパーです。
当時私は、3つのパラメーター (XML文書の入力、XSLTスタイル・シート、および出力の文書) を収集する必要があったため、図1 に示すような、3つの入力フィールド、1つの「変換」ボタン、およびエラー・メッセージ用の大きな表示域のみが必要であると考えていました。
図1. あまり良くないユーザー・インターフェース
このインターフェースを何か月か使っているうちに、どこが良くてどこが悪いのかが、かなり把握できました。まず最初に、良い点として、機能的で、大変説明しやすい点が挙げられます。残念ながら、このインターフェースには、次のようないくつかの問題点もあります。
- 扱いにくい。似たようなインターフェースを使用したことのあるユーザーは、私の言う意味がお分かりだと思います。このインターフェースは、単一のスタイル・シートを使用して1つのXML文書のみを処理する場合には、うまく機能します。現実には、複数の文書と複数のスタイル・シートを使用するのが一般的です。同じファイルを選択してはまた再度選択する、というのでは面倒でしかたがありません。
- フィールドを混同して、XML文書をXSLフィールドに入れてしまったり、XSL文書をXMLフィールドに入れてしまったりする可能性が高い。
- 誤ってファイルを上書きしてしまう可能性も高い。
私は、ユーザー・インターフェースを使用する必要があることが分かっていましたので、今いるPineapplesoft社でXSLT処理プログラムの使用方法を研究しました。そして、3つの異なるケースがあることを発見しました。
- スタイル・シートのデバッグ
- 手作業によるファイルの処理
- バッチの実行
私たちは、デバッグを行うときに、決まったファイルのセットに対してスタイル・シートを繰り返し設定します。分かりやすいエラー・メッセージと、ファイルを選択するための便利な方法が必要です。出力ファイルが固有のディレクトリーに入っていると楽です。2回に1回程度の割合で、テスト・ケースをすべて保存する必要があります。一方、残りの半分のケースでは、最後のテストで直前のテスト・ケースを消去しても問題ありません。
ファイルを手作業で変換するときには、多くの場合、十分にテストされた2つまたは3つのスタイル・シートを使用します。正しいスタイル・シートを簡単に選択できることは、きわめて重要です。
バッチ作業を行うときには、すべてのパラメーターをコマンド行から設定します。多くの場合、私たちは、APIレベルで機能する特別なバージョンのソフトウェアを使用します。ただし、この場合、私が優先したのはバッチ・インターフェースではありませんでした。
とにかく単純に
あれこれと考えた結果、最上の解決策は、既存のインターフェースを拡張してファイルをドラッグ・アンド・ドロップできるようにすることであるという結論に達しました。ファイルがさまざまなフィールドにドロップできるようになると、スタイル・シートの繰り返し操作やファイル選択が楽になると考えたからです。
ドラッグ・アンド・ドロップはJDK 1.2で導入されていて、非常に簡単に使用することができます。DropTargetListener インターフェースを実装して、通常のコンポーネントをドロップのターゲットにするだけで済みます。
私はすぐに簡単なテストを行い、どうしてもファイルを正しい場所にドロップできないことを知りました。図2 はそのようすを示しています。私は、誤ってスタイル・シートを2つのフィールドにドロップしてしまいました。
図2. ドラッグ・アンド・ドロップは使えるか?
確かにドラッグ・アンド・ドロップは、ファイル選択を単純化してくれるのですから、良いアイデアです。しかし、フィールドが3つもある必要はありません。1つで十分です。また、ドラッグ・アンド・ドロップは、出力ファイルの場合にはあまり役に立ちません。そこで、今度こそ究極のインターフェースの登場となります。
私は、ユーザー・インターフェースを徹底して単純化し、オプションとコントロールを少なくするほど混乱が減ると、固く信じています。図3 に示すように、私はフィールドを全部取り除きました。実際、ドロップダウン・リスト・ボックスが十分にスタイル・シート・フィールドの代わりになることが分かりました。このインターフェースは、rules ディレクトリーにあるすべてのファイルをリストします。これは、少数のスタイル・シートを順に繰り返す場合には、特に便利です。このアプリケーションは、出力ファイルの名前を自動的に生成します。テキスト・フィールドがないので、入力ファイルをウィンドウ内のどこにドロップしても構わないわけです。このほうが、小さなテキスト・フィールドに的を合わせるよりも、はるかに簡単です。私はまた、ドラッグ・アンド・ドロップが不便な場合に備えて「Open (開く)」ボタンを用意しました。
図3. より良いユーザー・インターフェース
私はまだ、このインターフェースを、自分で納得できるほど徹底してテストしてはいません。そこで、メーリング・リストで読者のコメントをお寄せいただきたいと思っています (参考文献を参照)。以下のような、若干のオプションが必要であることが分かっています。
- 出力ディレクトリー内のファイルを上書きまたは保存するオプション (デバッグの際に役立ちます)。
- 呼び出しを行うごとにスタイル・シートを再ロードするオプション(スタイル・シートの更新中にデバッグを行う場合に役立ちます)。
- 最後の変換の後にソフトウェアを閉じるオプション (主にバッチ・モードで使用します)。
また、入力文書を設定し、スタイル・シートを選択し、オプションを変更するためのコマンド行引数も用意しました。
結論
今回の記事では、テキスト文書をXMLに変換するツールの作成方法を示しました。過去の経験から、私には、レガシー文書をXMLに変換したいだけの場合、このようなツールが非常に便利であることが分かっています。いつもと同じように、このアプリケーションのソース・コードは、developerWorks のオンライン・ソース・リポジトリーに載せてあります。XIは、ライブラリーとして読者のプロジェクトに組み込むことも (おなじみのXMLReader APIを使用します)、ユーザー・インターフェースに作り替えて、データ変換が必要な場合に使用することもできます。
参考文献
著者について  | 
|  | Benoit Marchal氏は、ベルギーのナミュールを拠点にしたコンサルタントおよび著述家です。彼の著作には、 XML by Example(Que社、邦訳: インプレス社「実例で学ぶXML」。間もなく第2版が出版される予定です)、 Applied XML Solutions および XML and the Enterprise があります。また、Gamelanのコラムや、developerWorks XML zoneのコラムWorking XML の著者でもあります。最新プロジェクトの詳細については、www.marchal.com をご覧ください。 |
記事の評価
|