連載 役に立つXMLツール集(5)
スキーマコンバータ/バリデータ/ビジターパターン

XMLプログラミングでは、DOMやSAXといったAPIを使用すると単調なコードを繰り返し書くことになり生産性が上がらないものだ。本連載では開発者が“楽をする”ために役立つXML関連ツールを紹介していく。(編集局)

www.netpotlet.com
原田洋子
2004/3/9

3つの便利な小道具たち

主な内容
3つの便利な小道具たち
スキーマコンバータ「Trang」
バリデータ「MSV
「Relaxer」のビジターパターン生成機能
まとめ
付録 コンパクトキーボードの実行方法

 前回までに、JAXBCastorRelaxerと3種類のデータバインディングツールを紹介してきましたが、いかがだったでしょうか。今回は、データバインディングツールによるO/Rマッピングの予定でしたが、ちょっと寄り道してタイトルに並べた小道具を3つ紹介します。これらは、データバインディングツールを使って何かをするときにとても便利な道具たちです。こういった小道具を組み合わせると、データバインディングツールをもっと楽に、うまく使いこなせるようになるはず……。ぜひ試してください。

スキーマコンバータ「Trang」

 まずは、DTDRELAX NGへ、RELAX NGをW3C XML Schemaへというように違うスキーマ言語による定義に変換してくれるコンバータ「Trang」です。この連載でも第2回の「JAXBでデータバインディングに挑戦しよう」 、第3回の「Castorでデータバインディングに挑戦しよう」 で使っていましたが、影の存在でしたので、ここで表舞台に引っ張り出すことにしました。

Trangとは

 Trangはジェームズ・クラーク(James Clark)氏が作ったオープンソースのスキーマコンバータです。クラーク氏はXSLTやRELAX NGをはじめ、数々の仕様策定にかかわってきたばかりではなく、コンバータやバリデータなどの優秀なXML関連ツールもリリースしているので、XMLに携わる人の間ではおなじみの名前でしょう。なお、Trangは表1に示す入力、出力に対応しているので、コンバータのカバー範囲としては実用上、問題はないでしょう。

入力 拡張子   出力 拡張子
RELAX NG(XML syntax) .rng   RELAX NG(XML syntax) .rng
RELAX NG(compact syntax) .rnc   RELAX NG(compact syntax) .rnc
XML 1.0 DTD .dtd   XML 1.0 DTD .dtd
----- ---   W3C XML Schema .xsd
XML 文書 .xml   ----- ---
表1 Trangが対象にしている形式


Trangのインストールと実行

 TrangはJavaアーカイブを含むzip形式のほか、RPM形式でも配布されています。筆者が利用しているSuSE LinuxにはTrangが入っていますが、FreeBSDではportsに入れられる動きがあり、近い将来、インストールしなくても使えるようになるかもしれません。

 Javaアーカイブをインストールして実行する場合は、

  1. trang-20030619.zipを取得する
  2. trang-20030619.zipを適当なディレクトリで展開する
    例えば、/usr/local/javaに展開したとすると、/usr/local/java/trang-20030619ディレクトリが作られる。ここにTrangのアーカイブtrang.jarがある
  3. 実行する

 実行例1 RELAX NGをW3C XML Schemaに変換(拡張子に依存)

> java -jar /usr/local/java/trang-20030619/trang.jar
keyboard.rng keyboard.xsd
(表示の都合で改行しています)
コンソール1 RELAX NG→W3C XML Schema

 実行例2 XML文書をW3C XML Schemaに変換(形式指定)

> java -jar /usr/local/java/trang-20030619/trang.jar
-I xml -O xsd keyboard.xml keyboard.xsd
(表示の都合で改行しています)
コンソール2 XML文書→W3C XML Schema

 RPM形式をインストールする場合はtrang-20030619-1.i386.rpmを取得します。インストール作業は通常のRPMと同じなので、説明を省きます。こちらはLinuxの実行形式になっているので、Trang実行時にJava VMは必要ありません。インストール後、引き数を指定しないでtrangコマンドを実行すると次のようにメッセージが表示されます。

> trang
fatal: at least two arguments are required
Trang version 20030619
usage: trang [-I rng|rnc|dtd|xml] [-O rng|rnc|dtd|xsd] [-i input-param] [-o output-param] inputFileOrUri ... outputFile
コンソール3 Linux RPMでの使用方法

 このメッセージにあるとおり、使い方はJavaアーカイブとほぼ同じです。

 Trangにはここで紹介したほかにもいくつかオプションがあるので、付属のドキュメントtrang-manual.htmlかオンラインドキュメントを参照してください。

Trangの使いどころ

 データバインディングツール初心者にとって、ツールが要求するスキーマ言語で自分が思い描くスキーマを書くのは簡単ではありません。そのような場合に、コンバータがあるとDTDやXML文書からスキーマを作り出してくれるので、これを利用してもいいですし、どのように書けばいいのかを勉強することもできます。CastorもRelaxerもXML文書からオブジェクトモデルへのマッピングができるようになっていますが、定義を自分で書けるようになると適用範囲が広がるので、コンバータを使いながらスキーマの書き方を身に付けていくといいのではないでしょうか。

バリデータ「MSV」

 次に紹介する小道具はバリデータ(検証ツール)です。データバインディングツールはスキーマで定義されているとおりにXML文書を書かないと正しく読み込んでくれません。初心者のうちは自分が定義したはずのスキーマでもそのとおりにXML文書を書けなかったり、タイプミスしてしまったりといったことがあります。特に要素の開始タグをタイプミスしているのにコピー&ペーストして終了タグの“/”を足していると、XML文書としては正しい整形式になっているので間違いになかなか気が付かないことがあります。そこで必要になってくるのがバリデータです。ここでは、「MSV」というバリデータを紹介します。

MSVとは

 MSVは正式名称では「Sun Multi-Schema XML Validator」といいます。サン・マイクロシステムズ社の川口耕介氏が作ったバリデータで、Sunのサイトから配布されていますが、オープンソースです。このため、サポートも川口氏が個人的に行っているのみです。

 対応しているスキーマ言語は

  • DTD
  • RELAX NG
  • W3C XML Schema Part 1のサブセット
  • TREX
  • RELAX Namespace
  • RELAX Core

となっています。

MSVのインストールと実行

 MSVのインストール作業はアーカイブを取得して展開するだけです。例えば、/usr/local/javaディレクトリに展開したとすると、msv-20030225のような日付の付いたディレクトリが作られ、一式が置かれます。ここにあるmsv.jarがMSVのJavaアーカイブです。

 MSVを実行してみましょう。引数を指定しないで実行すると使い方が表示されます。

> java -jar /usr/local/java/msv-20030225/msv.jar
使い方: java -jar msv.jar <opts> <スキーマ> <文書1> [<文書2> ...]

オプション:
  -standalone: スキーマや文書に指定されているexternal DTDを無視します
  -strict    : スキーマをより厳密にエラーチェックします
  -dump      : 検証ではなくスキーマをダンプします
  -verbose   : 様々な情報を追加出力します
  -maxerror  : 間違ったエラーを出す危険を冒してでも多くのエラーを報告します
  -warning   : 警告メッセージも表示します
  -catalog <カタログファイル>
             : 外部entityの参照解決にTR9401カタログファイルを使用します
               詳細はhttp://www.sun.com/xml/developers/resolver/を見てください
  -version   : バージョン番号を表示します
  -locale <ロケール>
             : メッセージの言語を指定します(例:"en","ja")
  -xerces    : Xerces-JをXMLパーサとして使います
  -crimson   : CrimsonをXMLパーサとして使います
  -oraclev2  : Oracle V2パーサをXMLパーサとして使います
コンソール4 MSVの使用方法

 手始めに、前回の「Relaxerでデータバインディングに挑戦しよう」で作ったkeyboard.rngとkeyboard2.xmlで試してみます。すると、次のようにkeyboard2.xmlがバリッドな文書であることが分かります。

> java -jar /usr/local/java/msv-20030225/msv.jar -verbose schemas/keyboard.rng docs/keyboard2.xml
Multi Schema Validator Ver.20030225
パーサ:org.apache.xerces.jaxp.SAXParserFactoryImpl
XMLに指定されているDTDも使います
スキーマを読み込んでいます...
スキーマ読み込み完了 (749 ms)
検証しています: docs/keyboard2.xml
文書は妥当(valid)です
検証完了 (194 ms)
コンソール5 MSVでのバリデーション例1

 keyboard.rngの構造はシンプルですから、これだけではあまりありがたみが分かりませんので、もう少し複雑な定義で試してみましょう。「Jing」というバリデータにはXHTMLをRELAX NGで定義したスキーマが付属しています。XHTML 1.0 Strictの定義xhtml-strict.rngを使ってどのようなメッセージが表示されるかを見てみます。

 例えばリスト1のImageMap.htmlに示すXHTMLですが、このXHTMLには2カ所、正しくないところがあります。

 1 <?xml version="1.0"?>
 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 3   <head>
 4     <title>Demo</title>
 5     <meta http-equiv="Content-Type" content="text/html"/>
 6   </head>
 7   <body style="background: white;">
 8     <a href="/demo/index.html">Top</a><br/>
 9     <table cellpadding="0" cellspacing="0" summary="frame"
10            style="margin-top: 10px; border-width: 3px;
11            border-style: solid; border-color: #4f4f72;">
12       <tr>
13          <td align="center">
14           <img alt="Map" usemap="imagemap"
15                src="/demo/map.jpg"
16                style="margin: 5px; border-width: 0px;"/>
17           <map id="imagenap">
18             <area alt="taro" shape="rect" coords="11,16,63,54"
19 onmouseover="document.mapImage.src='/demo/taro.jpg';"
20 onmouseout="document.mapImage.src='/demo/map.jpg';"
21              href="/demo/taro.html"/>
22             <area alt="hana" shape="circle" coords="197,32,28"
23 onmouseover="document.mapImage.src='/demo/hana.jpg';"
24 onmouseout="document.mapImage.src='/demo/map.jpg';"
25              href="/demo/hana.html"/>
26           </map>
27         </td>
28       </tr>
29     </table>
30   </body>
31 </html>
リスト1 ImageMap.html(間違いが2カ所ある)

 MSVを実行してみると、次のようなメッセージが表示されました。

> java -jar /usr/local/java/msv-20030225/msv.jar -verbose
/usr/local/java/jing-20030619/doc/xhtml/xhtml-strict.rng ImageMap.html
Multi Schema Validator Ver.20030225
パーサ:org.apache.xerces.jaxp.SAXParserFactoryImpl
XMLに指定されているDTDも使います
スキーマを読み込んでいます...
スキーマ読み込み完了 (2,051 ms)
検証しています: ImageMap.html
エラー 8行目31文字目
: file:///home/yoko/niue/workspace/creek/tmp/ImageMap.html
  要素"a"はここには書けません。書けるのは: <address>,
<blockquote>,<del>,<div>,<dl>,<fieldset>,<form>,<h1>,<h2>,
<h3>,<h4>,<h5>,<h6>,<hr>,<ins>,<noscript>,<ol>,<p>,<pre>,
<script>,<table>,<ul>

エラー 31行目1文字目
: file:///home/yoko/niue/workspace/creek/tmp/ImageMap.html
  "imagemap"はIDREFによって参照されていますが、定義されていません

文書に違反がみつかりました
検証完了 (134 ms)
コンソール6 MSVでのバリデーション例2(1〜2行は本来1行です)

 エラーが2つ見つかっています。1つ目は、XHTML 1.0 Strictではbody要素の子どもにa要素を書けないのでエラーになったものです。ここはメッセージにあるとおり、例えばdivやp要素で囲むと正しくなります。2つ目はリスト1の14行目のusemap属性で参照しているimagemapが最後まで行っても見つからなかったというエラーです。17行目のid属性が"imagenap"(mapのはずがnapで終わっている)とタイプミスしていることが理由です。"imagemap"に直せば正しくなります。

 もう1つ、スキーマのチェックについても見てみましょう。RELAX NGにはincludeという機能がありますが、これは外部ファイル内の定義を参照するために使う場合ともう1つ、再定義のためにも使います。Java言語に例えると、継承した親クラスのメソッドをオーバーライドするようなものです。例えば、フォームのinputタグの子どもの要素にconfigを書けるように定義を拡張してみます。そのためには、例えばリスト2のmy-config.rngで追加するconfig要素を定義し、リスト3のmy-xhtml.rngでXHTMLへの組み込みを定義します。

 さて、リスト3には1カ所、間違いを混入させました。今度どんなメッセージが表示されるでしょうか。検証するのはリスト4のFormSample.htmlです。

 1 <grammar xmlns="http://relaxng.org/ns/structure/1.0"
 2  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
 3  ns="urn:MyConfig">
 4
 5   <define name="config">
 6     <element name="config">
 7       <ref name="config.attlist"/>
 8     </element>
 9   </define>
10
11   <define name="config.attlist">
12     <optional>
13       <attribute name="fieldName"/>
14     </optional>
15     <optional>
16       <group>
17         <attribute name="actionName"/>
18         <attribute name="navigation">
19           <data type="boolean"
20 datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
21         </attribute>
22       </group>
23     </optional>
24   </define>
25
26 </grammar>
リスト2 my-config.rng

 1 <grammar xmlns="http://relaxng.org/ns/structure/1.0"
 2          ns="http://www.w3.org/1999/xhtml">
 3
 4   <include href="my-config.rng"/>
 5
 6   <include href="xhtml/xhtml-strict.rng">
 7     <define name="imput">
 8       <element name="input">
 9         <ref name="input.attlist"/>
10         <optional>
11           <ref name="config" ns="urn:MyConfig"/>
12         </optional>
13       </element>
14     </define>
15   </include>
16
17 </grammar>
リスト3 my-xhtml.rng(間違いが1カ所ある)

 1 <?xml version="1.0" encoding="EUC-JP"?>
 2   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja"
 3         xmlns:my="urn:MyConfig">
 4     <head>
 5       <title>Form Sample</title>
 6       <meta http-equiv="Content-Type"
               content="text/html; charset=euc-jp"/>
 7     </head>
 8     <body>
 9       <form method="post" action="/demo/subscribe.html">
10         <p>
11           メールアドレス: <input name="email" type="text">
12             <my:config fieldName="user.email"/>
13           </input><br/>
14           <input type="submit" value="Subscribe">
15             <my:config actionName="update"
                          navigation="false"/>
16           </input>
17         </p>
18       </form>
19     </body>
20 </html>
リスト4 FormSample.html

 MSVを実行してみましょう。すると、次のようなメッセージが表示されます。

> java -jar /usr/local/java/msv-20030225/msv.jar -verbose my-xhtml.rng FormSample.html
Multi Schema Validator Ver.20030225
パーサ:org.apache.xerces.jaxp.SAXParserFactoryImpl
XMLに指定されているDTDも使います
スキーマを読み込んでいます...
パターン"imput"はオーバーライドされていますが、そもそも元の定義がありません
  6:40@file:///home/yoko/niue/workspace/creek/tmp/my-xhtml.rng
スキーマの読み込みに失敗しました
コンソール7 MSVでのバリデーション例3(1〜2行は本来1行です)

 間違いが発見されました。リスト3のスキーマは7行目でタイプミスしていたのです。

誤 7     <define name="imput">
正 7     <define name="input">

 そこで、上記のようにimputをinputに修正して再度、MSVを実行すると次のようなメッセージが表示され文書がバリッドであることが分かります。

> java -jar /usr/local/java/msv-20030225/msv.jar -verbose my-xhtml.rng FormSample.html
Multi Schema Validator Ver.20030225
パーサ:org.apache.xerces.jaxp.SAXParserFactoryImpl
XMLに指定されているDTDも使います
スキーマを読み込んでいます...
スキーマ読み込み完了 (1,618 ms)
検証しています: FormSample.html
文書は妥当(valid)です
検証完了 (102 ms)
コンソール8 MSVでのバリデーション例4(1〜2行は本来1行です)

MSVの使いどころ

 世の中にはバリデータというツールが多数あり、Trangの作者であるクラーク氏も「Jing」というバリデータをリリースしています。Jingは速いという定評がありますが、検証した文書がバリッドではなかったときのメッセージがぶっきらぼうで、間違っているのは確かに分かるのですが、どう間違っていて、どうすればいいのかが初心者にはよく分かりません。この点、MSVは懇切丁寧に、しかも日本語で教えてくれるので初心者でもスキーマの間違いやXML文書のミスを間単に見つけられます。

 データバインディングツールと一緒に使うのはもちろんですが、単にXHTML文書の検証を行うために使ってもいいのではないでしょうか。

「Relaxer」のビジターパターン生成機能

 最後に紹介する小道具は前回紹介したRelaxerが提供する機能の1つを利用するもので、Relaxerをビジターパターン生成ツールとして使用する方法です。

ビジターパターン生成機能とは

 ビジターパターンというのはGoF(The Gang of Four;Erich Gamma、Richard Helm、John Vlissides、Ralph Johnson)が執筆した『Design Patterns: Elements of Reusable Object-Oriented Software』(邦訳:『オブジェクト指向における再利用のためのデザインパターン』)で紹介されているビジターパターンを指しています。

 ビジターパターンはオブジェクトの振る舞いに関するパターンの1つで、ツリー構造を走査しながらツリーの各要素でオペレーションを実行する場合に必要になるパターンです。ビジターパターンの場合、名前のとおりビジターと呼ばれるオブジェクトを設けます。図1に概略図を示しましたが、ビジターはツリーを構成する各オブジェクトを訪問して歩きながら、許可されているメソッドを実行していくモデルです。

 図1 ビジターパターン

 Relaxerにはビジターパターンを構成するクラスを生成する機能が用意されています。この機能を利用する場合は、実行時に-java.pattern.visitorオプションを指定しますが、このとき従来の文書操作クラスに加えて、IRNode、IRVisitable、URVisitor、IRVisitor、RVisitorBaseの各クラスも作られます。この中で、IRVisitorはビジターとなるクラスが実装しなければならないインターフェイスで、ツリー構造に各ノードのオブジェクトを引数に持つ、enter()/leave()メソッドが定義されています。これらのメソッドがどのタイミングで実行されるかですが、図2に示すように各要素の開始タグでenter()メソッドを、終了タグでleave()メソッドを実行すると考えると分かりやすいでしょう。

 図2 ビジターが実行するメソッド

 IRVisitorインターフェイスはデフォルトの実装も提供されています。それがRVisitorBaseというクラスです。つまり、通常はRVisitorBaseを継承して必要なメソッドをオーバーライドしてビジターを作ります。

ビジターパターンを利用したアプリケーション

 この連載の第1回「XMLをJavaにマップするデータバインディング」で、XMLの世界にJavaを持ち込めると何がウレシイのかに触れ、

キーボードは飾りものではありませんので、「う〜ん、キーボードをこのような構造でとらえると美しいねぇ」と構造を表現しただけで終わっていては意味がありません。“Z”を押したら、Zという文字が入力されなければなりませんし、“スペース”を押したら、空白文字が入力されたり、仮名漢字変換が実行されなければなりません。

と説明しました。連載第2〜4回まではデータバインディングツールの使い方を見てきただけで、XMLモデルからJavaにマップされるありがたみがあまりなかったかもしれませんので、今回は「キーを押したら文字が入力される」アプリケーションにトライしてみます。また、このアプリケーションを実装するときにビジターパターンを利用してみます(サンプルプログラムはページ末尾からダウンロードできます)。

 では、アプリケーションが目指すゴールを考えます。このアプリケーションでは図3に示すようなキーボードを作ることにします。本記事ではこのキーボードを“コンパクトキーボード”と名付けることにしました。

 図3 コンパクトキーボード

 コンパクトキーボードでは

  1. ディスプレイ付き。入力された文字はディスプレイに表示される
  2. キーを押すとキートップに表示される文字が入れ替わると同時にディスプレイの表示も入れ替わる
  3. 同一キーにマップされている文字を連続して入力する場合、矢印キー(制御キー)を押してディスプレイの表示位置を進める
  4. 制御キーを押した場合、ディスプレイにキートップの文字を表示しない

という機能の実装を試みます。

 アプリケーションの仕様がほぼ固まりましたので、データバインディングツールを使って、実装を進めていきましょう。なお、本記事のプログラムは

  • J2SDK 1.4.1_06
  • Relaxer 1.1b(20040218)

を使用し、Linux上で動作を確認しています。

・スキーマの作成

 データバインディングを利用するのでスキーマの定義から始めます。ここでも、前回のRelaxerで紹介した定義keyboard.rngをそのまま使えるとよかったのですが、残念ながらそのままではコンパクトキーボードには当てはまりません。足りないのです。

 コンパクトキーボードにはディスプレイがあります。また、キーボードの配置情報もできればXMLモデルから持っていたいところです。これらをスキーマに盛り込むとすると再定義が必要になりますが、このような場合に便利なのが、RELAX NGのincludeの機能です。MSVの説明でも触れたオーバーライドという方法で、元の定義に手を入れず新たな定義を追加できます。

 コンパクトキーボードのスキーマはリスト5のkeyboard.rngに加えて新たにリスト6のcompact-keyboard.rngを定義しました。リスト5は前回のRelaxerで使ったkeyboard.rngと同じですが、再定義を行うのでkeyboard要素の定義にdefineを使って外部から参照できるようにしました(13行目)。

 リスト6ではkeyboard要素がdisplay要素を持てるようにし(7行目)、key要素はposition要素を持てるようにしました(10行目)。position要素は新規なのでリスト6で定義しています(19〜23行目)。

 リスト6のcompact-keyboard.rngに従って記述したXML文書がリスト7のkeyboard.xmlです。これが図3のコンパクトキーボードそのものです。

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <grammar xmlns="http://relaxng.org/ns/structure/1.0"
 3  xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
 4  xmlns:relaxer="http://www.relaxer.org/xmlns/relaxer"
 5  xmlns:java="http://www.relaxer.org/xmlns/relaxer/java"
 6  xmlns:sql="http://www.relaxer.org/xmlns/relaxer/sql"
 7  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
 8  ns="">
 9   <start>
10     <ref name="keyboard"/>
11   </start>
12
13   <define name="keyboard">
14     <element name="keyboard">
15       <zeroOrMore>
16         <element name="key">
17           <optional>
18             <ref name="size"/>
19           </optional>
20           <oneOrMore>
21             <ref name="label"/>
22           </oneOrMore>
23         </element>
24       </zeroOrMore>
25     </element>
26   </define>
27
28   <define name="size">
29     <choice>
30       <attribute name="size">
31         <ref name="size.list"/>
32       </attribute>
33       <element name="size">
34         <ref name="size.list"/>
35       </element>
36     </choice>
37   </define>
38
39   <define name="size.list">
40     <choice>
41       <value>large</value>
42       <value>medium</value>
43       <value>small</value>
44     </choice>
45   </define>
46     
47   <define name="label">
48     <element name="label">
49       <optional>
50         <attribute name="default">
51           <data type="boolean"
52 datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
53         </attribute>
54       </optional>
55       <choice>
56         <attribute name="type">
57           <ref name="type.list"/>
58         </attribute>
59         <element name="type">
60           <ref name="type.list"/>
61         </element>
62       </choice>
63       <text/>
64     </element>
65   </define>
66
67   <define name="type.list">
68     <choice>
69       <value>control</value>
70       <value>character</value>
71       <value>number</value>
72     </choice>
73   </define>
74
75 </grammar>
リスト5 keyboard.rng

 1 <grammar xmlns="http://relaxng.org/ns/structure/1.0"
 2  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
 3
 4 <include href="keyboard.rng">
 5   <define name="keyboard">
 6     <element name="keyboard">
 7       <element name="display"><ref name="position"/> </element>
 8       <zeroOrMore>
 9         <element name="key">
10           <ref name="position"/>
11           <optional><ref name="size"/></optional>
12           <oneOrMore><ref name="label"/></oneOrMore>
13         </element>
14       </zeroOrMore>
15     </element>
16   </define>
17 </include>
18
19 <define name="position">
20   <element name="position">
21     <ref name="style"/>
22   </element>
23 </define>
24
25 <define name="style">
26   <attribute name="x">
27     <data type="integer"
28 datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
29   </attribute>
30   <attribute name="y">
31     <data type="integer"
32 datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
33   </attribute>
34   <optional>
35     <attribute name="width">
36       <data type="integer"
37 datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
38     </attribute>
39   </optional>
40 </define>
41
42 </grammar>
リスト6 compact-keyboard.rng

 1 <?xml version="1.0" encoding="EUC-JP"?>
 2 <keyboard>
 3     <display>
 4       <position x="0" y="0"/>
 5     </display>
 6     <key>
 7         <position x="0" y="1" width="1"/>
 8         <label type="character" default="true">あ</label>
 9         <label type="character">い</label>
10         <label type="character">う</label>
11         <label type="character">え</label>
12         <label type="character">お</label>
13         <label type="character">ぁ</label>
14         <label type="character">ぃ</label>
15         <label type="character">ぅ</label>
16         <label type="character">ぇ</label>
17         <label type="character">ぉ</label>
18     </key>
19     <key>
20         <position x="1" y="1" width="1"/>
21         <label type="character" default="true">か</label>
22         <label type="character">き</label>
23         <label type="character">く</label>
24         <label type="character">け</label>
25         <label type="character">こ</label>
26     </key>
27     <key>
28         <position x="2" y="1" width="1"/>
29         <label type="character" default="true">さ</label>
30         <label type="character">し</label>
31         <label type="character">す</label>
32         <label type="character">せ</label>
33         <label type="character">そ</label>
34     </key>
35     <key>
36         <position x="0" y="2" width="1"/>
37         <label type="character" default="true">た</label>
38         <label type="character">ち</label>
39         <label type="character">つ</label>
40         <label type="character">て</label>
41         <label type="character">と</label>
42         <label type="character">っ</label>
43     </key>
44     <key>
45         <position x="1" y="2" width="1"/>
46         <label type="character" default="true">な</label>
47         <label type="character">に</label>
48         <label type="character">ぬ</label>
49         <label type="character">ね</label>
50         <label type="character">の</label>
51     </key>
52     <key>
53         <position x="2" y="2" width="1"/>
54         <label type="character" default="true">は</label>
55         <label type="character">ひ</label>
56         <label type="character">ふ</label>
57         <label type="character">へ</label>
58         <label type="character">ほ</label>
59     </key>
60     <key>
61         <position x="0" y="3" width="1"/>
62         <label type="character" default="true">ま</label>
63         <label type="character">み</label>
64         <label type="character">む</label>
65         <label type="character">め</label>
66         <label type="character">も</label>
67     </key>
68     <key>
69         <position x="1" y="3" width="1"/>
70         <label type="character" default="true">や</label>
71         <label type="character">ゆ</label>
72         <label type="character">よ</label>
73         <label type="character">ゃ</label>
74         <label type="character">ゅ</label>
75         <label type="character">ょ</label>
76     </key>
77     <key>
78         <position x="2" y="3" width="1"/>
79         <label type="character" default="true">ら</label>
80         <label type="character">り</label>
81         <label type="character">る</label>
82         <label type="character">れ</label>
83         <label type="character">ろ</label>
84     </key>
85     <key>
86         <position x="1" y="4" width="1"/>
87         <label type="character" default="true">わ</label>
88         <label type="character">を</label>
89         <label type="character">ん</label>
90         <label type="character">ゎ</label>
91         <label type="character">ー</label>
92     </key>
93     <key>
94         <position x="2" y="4" width="1"/>
95         <label type="control" default="true">→</label>
96     </key>
97 </keyboard>
リスト7 keyboard.xml

スキーマのコンパイル

 コンパクトキーボードのスキーマができたので、次はRelaxerを実行してXMLモデルをオブジェクトモデルにマッピングします。このときビジターパターンのためのクラスも生成させるために-java.pattern.visitorオプションも指定します。コマンドラインで実行する場合は次のようにします。

> relaxer -dir:src -dir.package -java.package:com.netpotlet.jetty
-java.pattern.visitor -verbose scheams/compact-keyboard.rng
(表示の都合で改行しています)

コンソール9 Relaxerの実行

・コンパクトキーボードアプリケーション作成

 スキーマをコンパイルしてAPIが生成されたので、今度はアプリケーションを作ります。ビジターパターンを使うので、ビジターとツリーの各要素に対応するクラスで構成するのが一般的です。しかし、このアプリケーションの場合、labelのtype属性(あるいは要素)によって動きの違うボタンを作りたいので、key要素に対しては2種類のクラスを定義し、

ビジタークラス KeyboardAssembler
ツリーのクラス MyDisplay
MyPosition
MyCharacterKey
MyControlKey
MyLabel

という構成にしました。

 ビジターのクラスKeyboardAssemblerだけ、どのような定義をしたかをリスト8に示します。図1、2で説明したように、ビジターはkeyboard要素を訪問し(22行目)、MyKeyboardクラスをインスタンス化して、ツリーの次の要素displayを訪問してMyDisplayクラスをインスタンス化して……というように処理を進めています。

 ビジターは基本的にツリーのすべての要素を走査しますが、このアプリケーションの場合key要素以下のサブツリーはまとめて処理した方が便利なので、これ以上ビジターが深入りしないように63行目でfalseを返しています。Relaxerが生成するビジターはこのようにenter()メソッドの戻り値で、ビジターの動きを制御できるようになっています。

 ほかのクラスについてはここでは触れませんので、記事末尾に掲載しているアーカイブ内のソースコードを参照してもらいたいのですが、ビジターを使わずにこのアプリケーションを作ろうとすると、forループやif分岐ばかりになってしまったり、ひたすらAWTコンポーネントをnewしてaddする長いメソッドを書いてしまったりになりがちです。ところが、このアプリケーションはどのクラスのメソッドも一目で何をしているのかが分かる程度に収まっています。こうなっていると可読性がいいばかりではなく、後の修正や追加も簡単です。

 1 package com.netpotlet.test;
 2
 3 import java.util.Stack;
 4
 5 import com.netpotlet.jetty.ILabelMixed;
 6 import com.netpotlet.jetty.Keyboard;
 7 import com.netpotlet.jetty.KeyboardDisplay;
 8 import com.netpotlet.jetty.KeyboardKey;
 9 import com.netpotlet.jetty.Label;
10 import com.netpotlet.jetty.LabelType1;
11 import com.netpotlet.jetty.LabelType2;
12 import com.netpotlet.jetty.RVisitorBase;
13
14 public class KeyboardAssembler extends RVisitorBase {
15     private Stack stack = new Stack();
16     private MyKeyboard myKeyboard = null;
17     
18     MyKeyboard getMyKeyboard() {
19         return myKeyboard;
20     }
21
22     public boolean enter(Keyboard visitable) {
23         myKeyboard= new MyKeyboard(visitable);
24         stack.push(myKeyboard);
25         return (true);
26     }
27
28     public void leave(Keyboard visitable) {
29     }
30
31     public boolean enter(KeyboardDisplay visitable) {
32         if (!stack.empty()) {
33             MyKeyboard myKeyboard = (MyKeyboard)stack.peek();
34             MyDisplay myDisplay =
35               new MyDisplay(visitable, myKeyboard);
36             stack.push(myDisplay);
37         }
38         return (true);
39     }
40
41     public void leave(KeyboardDisplay visitable) {
42         stack.pop();
43     }
44
45     public boolean enter(KeyboardKey visitable) {
46         MyKeyboard myKeyboard = (MyKeyboard)stack.peek();
47         Label label = visitable.getLabel(0);
48         ILabelMixed[] mixedLabels = label.getContent();
49         for (int i=0; i<mixedLabels.length; i++) {
50             ILabelMixed labelMixed = mixedLabels[i];
51             if ((labelMixed instanceof LabelType1) ||
52                 (labelMixed instanceof LabelType2)) {
53                 String type = labelMixed.getContentAsString();
54                 if ("control".equals(type)) {
55                     MyControlKey myControlKey =
56                         new MyControlKey(visitable, myKeyboard);
57                     return (false);
58                 }
59             }
60         }
61         MyCharacterKey myCharacterKey =
62           new MyCharacterKey(visitable, myKeyboard);
63         return (false);
64     }
65
66     public void leave(KeyboardKey visitable) {
67     }
68 }
リスト8 KeyboardAssemblerクラス

・コンパクトキーボードのカスタマイズ

 さて、図3を見ると、“いいてんきだなぁー”ではなく“いいてんきたなぁー”になっています。これはコンパクトキーボードを濁音を入力できるようにしていなかったためです。そこで、“だ”を入力できるようにしてみましょう。

 正統派なら濁点キーを追加するところですが、ここではリスト7のkeyboard.xmlに1行追加することで対応してみます。

37         <label type="character" default="true">た</label>
           <label type="character">だ</label>
38         <label type="character">ち</label>

 すると、今度は“いいてんきだなぁー”と入力できました(図4)。

 図4 コンパクトキーボード(“だ”を追加)

 XMLモデルから始めるようにしておくと、このようにちょっとしたカスタマイズならプログラムを修正しなくても、簡単に対応できるようになります。

まとめ

 データバインディング初心者にうれしいお助けツールをいくつか紹介しましたが、いかがだったでしょうか。データバインディングツールを使いこなせるようになるまでにはいくつか障害があるかもしれませんが、いろいろなツールを組み合わせると壁が低くなるのではないでしょうか。ここで紹介したほかにも便利な小物ツールはいろいろあるので、試してみるとこれは便利、というツールに出合うかもしれません。そうこうするうちにデータバインディングもかなり使いこなせるようになっていることでしょう。

 次回こそはデータバインディングツールによるO/Rマッピングを取り上げる予定です(次回に続く)。


付録 コンパクトキーボードの実行方法

 アーカイブを次のリンクから取得して、適当なディレクトリで展開してください。

 コンパクトキーボードを実行する場合、あらかじめRelaxerとAntをインストールしておいてください。

 Antのビルドファイルbuild.xmlはリスト9のようになっていますので、3行目のRelaxer、5行目のMSVインストールディレクトリを環境に合わせて修正するかantコマンド実行時に-Drelaxer.home=C:/relaxer/lib -Dmsv.home=C:/msvのように指定してください。

 1 <?xml version="1.0"?>
 2 <project name="creek" default="relaxer" basedir=".">
 3   <property name="relaxer.home"
               value="/usr/local/lib/relaxer"/>
 4   <property name="relaxer.jar"
               value="${relaxer.home}/Relaxer.jar"/>
 5   <property name="msv.home"
               value="/usr/local/java/msv-20030225"/>
 6   <property name="msv.jar" value="${msv.home}/msv.jar"/>
 7   <property name="schema.dir" value="schemas"/>
 8   <property name="docs.dir" value="docs"/>
 9   <property name="src.dir" value="src"/>
10   <property name="bin.dir" value="bin"/>
11   <property name="schema" value="compact-keyboard.rng"/>
12   <property name="doc" value="keyboard.xml"/>
13   <property name="package" value="com.netpotlet.jetty"/>
14   <property name="javafile.dir"
               value="${src.dir}/com/netpotlet/jetty"/>
15   
16   <target name="relaxer" depends="clean">
17     <java jar="${relaxer.jar}" fork="true">
18       <classpath>
19         <pathelement location="${relaxer.jar}"/>
20       </classpath>
21       <arg line="-dir:${src.dir} -dir.package"/>
22       <arg
   line="-java.package:${package} -java.pattern.visitor"/>
23       <arg line="-verbose"/>
24       <arg value="${schema.dir}/${schema}" />
25     </java>
26   </target>
27   
28   <target name="clean">
29     <delete>
30       <fileset dir="${javafile.dir}" includes="**/*.java"/>
31     </delete>
32   </target>
33   
34   <target name="execute" depends="compile,validate">
35     <java classname="com.netpotlet.test.CompactKeyboard"
36           classpath="${bin.dir}"
37           fork="true">
38       <arg value="${docs.dir}/${doc}"/>
39     </java>
40   </target>
41
42   <target name="compile" depends="prepare">
43     <javac srcdir="${src.dir}"
44            destdir="${bin.dir}"
45            deprecation="on" />
46   </target>
47
48   <target name="prepare">
49     <mkdir dir="${bin.dir}"/>
50   </target>
51
52   <target name="validate">
53     <java jar="${msv.jar}" fork="true">
54       <arg line="-strict -verbose"/>
55       <arg value="${schema.dir}/${schema}"/>
56       <arg value="${docs.dir}/${doc}"/>
57     </java>
58   </target>
59   
60 </project>
リスト9 build.xml

 アプリケーションは次のように実行します。

> unzip creek.zip
> cd creek

(Relaxer の実行)
> ant

(コンパクトキーボードの実行)
> ant execute

コンソール10 コンパクトキーボードの実行方法


関連記事

 ・SEのためのXML Schema入門
 ・XMLテクニック集
 ・Javaで実現するDOM/SAXプログラミング



「連載 役に立つXMLツール集」


XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間