チュートリアル1よりもう少し複雑な例を見てみます。
このチュートリアルで取り上げるスキーマは、あるディレクトリ以下のディレクトリとファイルの構造を表現したものです。以下のようになります。赤いところはRelaxNGCCのためのマークアップ部分です。青い数字は後述する説明に使います。
<?xml version="1.0" encoding="utf-8"?> <grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" xmlns:c="http://www.xml.gr.jp/xmlns/relaxngcc"> [1] <c:java-import> import java.util.Set; import java.util.HashSet; </c:java-import> <start c:class="sample2"> [2] <c:java-body> public Set hiddenfiles; </c:java-body> <element name="files"> <ref name="file-or-dir" c:alias="child"/> [3] <c:java>hiddenFiles = new HashSet(child.hiddenFiles);</c:java> </element> </start> [4] <define name="file-or-dir" c:class="FileOrDir"> <c:java-body> public Set hiddenFiles; </c:java-body> <c:java>hiddenFiles = new HashSet();</c:java> <oneOrMore> <choice> <element name="file"> <attribute name="name"> <text c:alias="filename"/> [5] <c:java>if(filename.startsWith(".")) hiddenFiles.add(filename);</c:java> </attribute> </element> <element name="directory"> <attribute name="name"><text/></attribute> <ref name="file-or-dir" c:alias="content"/> [6] <c:java>hiddenFiles.add(content.hiddenFiles);</c:java> </element> </choice> </oneOrMore> </define> </grammar>
このように、このスキーマではルートにfilesエレメントが来て、その下にdirectoryまたはfileが来ます。directoryの下にもdirectoryfileが来ます。
さて、このスキーマに則ったXML文書を読むとき、ピリオドで始まるファイル(Unixでは隠し属性のファイル)のみを集めてきたいと思ったとしましょう。ディレクトリ構造は無視して、ファイル名だけのコレクションを得ることが目的であるとします。
以下、各部分についての説明です。
[1] java-importの中に書いた文は、Javaのクラス定義の本体の外側に出力されます。javaエレメントやjava-bodyで使用するコードがimport文を必要とするならこの位置に書いてください。即ちimport文とコメントが書けることになります。packageの指定はgrammarエレメントのアトリビュートで行うので、java-import内に書くことはできません。なお、java-importは、grammarの直下に置いた場合出力されるすべてのJavaファイルに適用されます。単一のファイルに適用するときには対応するstartまたはdefineエレメントの直下に置いてください。
[2] java-bodyは、生成されるJavaコードに追加的なデータメンバやメソッドを定義したいときに使います。ここでは、ファイル名を収録するためにhiddenFilesという名前のメンバを宣言しています。
[3] javaエレメントは、XML文書を読んでスキーマの該当する位置に来たとき実行するコードを記述します。ここでは、ルートエレメントであるfilesが終わったら、hiddenFilesを設定しています。
[4] RelaxNGCCは、このブロック単位で対応する1個のJavaファイルを生成するので、このサンプルではJavaファイルは2つ生成されることになります。このクラス名を指定するのが、start、defineそれぞれに追加されたアトリビュート c:class です。特に、define側ではその名前がfile-or-dirでハイフンを含んでいるためそのままではJavaのクラス名としては不正です。そのようなときはc:classが必須になります。
[5] ピリオドで始まるファイル名であればコレクションに格納しています。
[6] これもjavaエレメントですが、直前のrefエレメントにあるaliasを参照しているところに注意してください。refエレメントにaliasをつけると、それはrefエレメントが参照するdefineブロックに対応したRelaxNGCCのオブジェクトになります。つまりこの例では、file-or-dirブロックに対応するFileOrDirオブジェクトということになります。
最終的にRelaxNGCCが出力したJavaファイルをコンパイル・実行すると、startエレメントに対応したオブジェクトのhiddenFilesメンバにすべての隠し属性ファイルが収録されます。起動手順は出力したファイルの中のmain()関数を参照してください。
文法からJavaのソースコードを出力する点では、RelaxNGCCとRelaxerは同じです。ですが、例えばこのサンプルで出したような目的でXML文書を読みたい場合、Relaxerを使ってXML文書を読み込ますと本来不要なdirectoryに対応したオブジェクトまでできてしまいます。もっと限定した情報がほしい場合、Relaxerの出力したオブジェクトモデルにアクセスしていかなければなりません。これに対しRelaxNGCCでは、すべてがSAXベースの1パスで処理が完了するため効率的です。
ただしJavaオブジェクトからXMLへの変換など、RelaxerにあってRelaxNGCCにない機能もあります。目的に応じて使い分ければよいでしょう。