RelaxNGCC独自のマークアップはすべて
http://www.xml.gr.jp/xmlns/relaxngcc
のnamespace-URIを持ちますが、このマニュアルではプレフィックス "c" を使用しています。マークアップは次に説明するように3種のエレメントと5種のアトリビュートから構成されます。
ユーザ定義のコードからアクセスするために、データに名前をつけます。この変数名で、生成されたクラス内にフィールドが追加されます。このアトリビュートをつけることのできるRELAX NGのエレメントは、data, text, ref, value, listの5つです。
<data type="nonNegativeInteger" c:alias="count"/>
ref/parentRefパターンにc:aliasをつけた場合、それらが参照する先のスコープで戻り値として指定された値が得られます(一般には、これはスコープに対応するRelaxNGCCが生成したクラスそのものです)。
listパターンにc:aliasをつけたときにはリスト全体が1個の文字列として取り出されます。
ユーザ定義のJavaコードを記述します。コードからc:aliasアトリビュートで定義した名前を使うことができます。記述したコードは、入力XMLの該当個所を読んだところで実行されます。
<element name="name"> <text c:alias="name"/> <c:java>System.out.println(name);</c:java> </element>
生成されたコードは最終的にはSAX2ベースの処理になるので、外に投げることのできる例外はSAXExceptionとその派生クラスに限られます。
javaエレメントはchoice, interleave以外の子パターンをとる全てのパターンの中に書けます(start, define, group, optional, zeroOrMore, oneOrMore, mixed, listパターンの中)。
なおエレメント名にjavaを使っている理由は、将来他のプログラム言語(例えばC#)にも対応するかもしれないからです。
生成されるクラスについて、補助的に使用するメソッドやデータメンバを記述します。ここで定義したものはjavaエレメント内のコードからアクセスできます。
<define name="x">
<c:java-body>
private void echo(String msg) {
System.out.println(msg);
}
</c:java-body>
...
</define>
たとえばこのようにすると、このdefineブロックのjavaエレメント内からechoメソッドが使えるようになります。
java-bodyエレメントは、次の場所に書くことができます。
java-bodyと違い、ソースコード中のクラス定義本体の外に記述するコードを記述します。ふつうJavaのimport宣言を記述します。これを置くことのできる位置と効果はjava-bodyと同じです。
<c:java-import> import java.util.Set; import java.util.Iterator; </c:java-import>
startエレメント、defineエレメントに記述して、それに対応するクラスの名前を指定します。Javaのクラス名として正しい文字列を指定する必要があります。省略時には、RelaxNGCCが適当な名前をつけてソースコードを生成します。
<start c:class="Root"> ... </start>
生成するソースコードがどのパッケージに属すかを指定します。ルートエレメントに記入します。この宣言は生成されるすべてのクラスに対して有効です。
<grammar ... c:package="com.example.project1"> ...
生成するソースコードのアクセス修飾子("public final"など)を指定します。このアトリビュートはdefineエレメント、startエレメント、classアトリビュートを書いたエレメントに書くことができます。
<start c:class="sample1" c:access="public final"> ... </start>
標準のNGCCRuntimeクラスの代わりに、この属性で指定されたユーザー定義のクラスを使うようにコンパイラに指示します。
この属性は、RELAX NG文法ファイルのルート要素でのみ指定可能です。
<?xml version="1.0"?> <grammar c:runtime-type="org.acme.foo.MyNGCCRuntime" ...> .... </grammar>
これらの属性を指定することで、生成されたハンドラクラスからの戻り値を指定できるようになります。これらの属性は、どちらも<define>か<start>パターンに対して指定可能です。
c:return-value属性には、戻り値として評価される式を書きます。子ハンドラが処理を終了して親ハンドラに戻る際に、この式が評価されて結果が親ハンドラに返ります。親ハンドラ側では、c:alias属性を使って戻り値にアクセスすることができます。c:return-type属性は、この戻り値の型を指定します。ディフォルトでは、c:return-valueは"this"になっているので、ハンドラ自身が返されます。
次の例では、makeResult関数が呼び出されて、その値が戻り値となります。
<define name="foo" c:return-type="String" c:return-value="makeResult()"> <c:java-body> private String makeResult() { .... } </c:java-body> ... </define> ... <ref name="foo" c:alias="someStringVariable">
これらの属性は対で利用され、親ハンドラから子ハンドラへパラメータを渡すのに使われます。
c:params属性は、<define>か<start>パターンに対して指定でき、パラメータを宣言します。一方、c:with-params属性は、パラメータつきで宣言された<define>を参照する<ref>パターンに対して指定します。
c:params属性の値は、カンマ区切りの型-変数名ペアで、ちょうど関数を宣言する時のパラメータリストと同じ書式です。c:with-paramsは、同じくカンマ区切りの式の並びで、やはりちょうど関数を呼び出す時と同じ書式です。
あるブロックをc:params付きで宣言すると、そのブロックを参照する全ての<ref/>パターンにc:with-paramsが必要になるので注意してください。
渡されたパラメータは、同名で生成されたフィールド変数にコピーされるので、<c:java-body>や<c:java>の中からアクセスできます。
<define name="foo" c:params="String a,boolean b,Object c"> ... <c:java> System.out.println(a); </c:java> </define> ... <ref name="foo" c:with-params='"xyz",true,null' /> ... <ref name="foo" c:with-params='"test",false,System.out' />
RelaxNGCCが生成したコードをコンパイルしたり実行するには、JAXP対応のXMLパーサが必要です
生成したコードは、SAX2イベントを生成するあらゆるコンポーネントと組み合わせて利用可能です。SAX2イベントから生成されたコードを駆動する方法については、もとの文法のstartエレメントに対応したクラスに付加されるmain()関数を参照してください。
RelaxNGCCはコンストラクタを自分で生成するため、そのままではコンストラクタを書き換えることはできません。オブジェクトの生成時の処理を追加するには、イニシャライザを利用してください。
<define name="foo"> <c:java-body> {// add your code here System.out.println("initializer"); } </c:java-body>
RelaxNGCCでは、どんな文法も読み込めるわけではなく、先読み1個で分岐を判断できる文法だけを受け付けます。
<choice> <group> <element name="a"><text/></element> <c:java>System.out.println("a0");</c:java> <element name="x"><text/></element> </group> <group> <element name="a"><text/></element> <c:java>System.out.println("a1");</c:java> <element name="y"><text/></element> </group> </choice>
このようなとき、最初にaエレメントをみただけではどちらのchoiceが正しいのかは判定できません。先読みをすればxまたはyがきて判定可能になりますが、RelaxNGCCはこのような判定はできません。次のように書き換える必要があります。
<group> <element name="a"><text/></element> <choice> <group> <element name="x"><text/></element> <c:java>System.out.println("a0");</c:java> </group> <group> <element name="y"><text/></element> <c:java>System.out.println("a1");</c:java> </group> </choice> </group>
文法がこの制約を満たさないとき、RelaxNGCCは警告メッセージを発します。特に文法があいまいな場合にはどう変形してもこの制約を逃れることができなくなるので注意が必要です。
情報科学の言葉で言えば、defineやstartブロックを、SAXイベントを終端記号とした文脈自由文法として解釈したとき、それがLL(1)であるときに限りRelaxNGCCは取り扱えます。このあたりの議論はコンパイラの教科書に詳しく載っています。
RELAX NGの仕様のうち、次の機能はまだサポートされていません。将来のバージョンで徐々に実装するつもりです。