本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

RELAX NGとカスタム・データ型ライブラリ

新しい型をJava技術で定義する

Elliotte Rusty Harold, Adjunct Professor, Polytechnic University
Photo of Elliot Rusty Harold
Elliotte Rusty Haroldはニューオーリンズ出身であり、時たま、おいしいgumbo(オクラ入りのスープ)を食べに帰っています。ただし現在はニューヨークのブルックリン近郊のProspect Heightsに、妻のBethと猫のCharm(charmed quarkからとりました)とMarjorie(義理の母の名前からとりました)と一緒に住んでいます。彼はPolytechnic Universityのコンピューター・サイエンスの非常勤教授として、Java技術とオブジェクト指向プログラミングを教えています。彼のCafe au Lait Webサイトは、インターネット上で最も人気のある独立系Javaサイトの一つです。また、そこから派生したCafe con Lecheは、最も人気のあるXMLサイトの一つです。彼の最近の著作には『Java I/O, 2nd edition』があります。現在はXML処理用のXOM APIやJaxen XPathエンジン、Jesterテスト・カバレッジ・ツールなどに取り組んでいます。

概要: RELAX NGは、W3C XML Schema言語でできることのほとんど全てのことができます。この中には、W3C XML Schemaの単純型で指定される、テキスト内容や属性値に対する制約の検証も含まれています。しかしながら、一部の制約についてはチューリング完全(Turing-complete)言語でなければ記述できず、RELAX NGはそうした言語ではありません。ところが幸いなことに、RELAX NG自体では規定できない制約をチェックする、(Java™プログラミング言語で書かれた)カスタム妥当性検証コードによって、RELAX NGを動的に拡張することができます。これには、DatatypeDatatypeLibrary、そしてDatatypeFactory という、3つのインターフェースの実装が必要になります。この記事では、ある数字が素数であることを検証する方法を通して、こうしたインターフェースを紹介します。

日付:  2004年 11月 23日
レベル:  上級 この記事の原文:  英語
アクティビティー: 1655 ビュー
お気軽にご意見・ご感想をお寄せください: 


RELAX NG XMLスキーマ言語は、過去3年間で大きな成功を収めました。この理由の主なものは、(特にW3C XML Schema言語と比べて)RELAX NGの持つ、信じがたいほどクリーンで単純な構文によるものです。OpenOffice や DocBook、そして Text Encoding Initiative などを含む多くのグループが、RELAX NGスキーマ言語を採用しています。RELAX NGはW3C内で、W3Cのスキーマすらも置き換え始めています。W3CではSVGとXHTMLのワーキング・グループが、共にそのスキーマをRELAX NGで書いており、その後でDTDやW3C XML Schemaに変換しています。RELAX NGでは、XMLスキーマ・データ型のサポートを強制していませんが、現実にはJingやSunのMultischema Validatorなどの主要な実装では、これをサポートしています。

同じことをするのであれば、W3C XML Schema言語よりもRELAX NGの方がずっと良い・・・、という興奮の声にもかかわらず、より多くのことがRELAX NGで可能だということが見過ごされてしまっています。特にRELAX NGは、W3C XML Schema言語とは異なり、拡張に対して事前に規定され制限された一つの基本データ型の集合に限定されているわけではありません。RELAX NGでは、開発者がプログラムで検証できるような任意の制約を自由に設定できる、タイプ・ライブラリを定義できるのです。例えばW3Cのスキーマでは、次のような制約の妥当性を検証することはできません。

  • ある数字が素数であるか否か。
  • 文字列の左括弧がすべて、右括弧と対応しているか否か。
  • SKU属性が、製品データベース中のレコードと対応しているか否か。
  • ある要素の内容が、(辞書ファイルと検証して)正しい綴りか否か。

こうした制約は、純粋なRELAX NGコードでも検証することはできません。しかし、W3C XML Schema言語とは異なり、JavaやC#、Pythonなど他の言語で書かれたユーザ定義のタイプ・ライブラリを使うことで、RELAX NGを拡張することができるのです。こうした言語は、チューリングが完全(参考文献参照)なので、文字列に対して課しうる制約条件は、どんなものでも検証できます。さらに文字列自体の枠を越えて、例えば倉庫の在庫や会社の株価など、外部条件との整合性までも比較することができます。つまりRELAX NGに組み込みの妥当性検証規則を、任意に合成された妥当性検証条件によって拡張することができるのです。

この記事では、そうした拡張のためのJavaインターフェースについて紹介します。こうしたインターフェースは、(JingやSunのMultischema Validatorを含めた)多くのRELAX NGプロセッサがサポートしているものです。ここでは具体例として、数字が素数であるか否かを検証します。素数かどうかを確認するコードは、RELAX NGにカスタム・ライブラリを組み込むためのサポート・コードとは、かなり独立しています。ですから、もっと複雑なアルゴリズムや妥当性検証条件をどこに差し込めばよいかが簡単に分かるでしょう。この拡張には、次の3つのクラスが必要です。

  • 素数データ型を表すorg.relaxng.datatype.Datatypeインターフェースを実装するクラス: このクラスは、与えられた文字列が実際に素数であることを確認する責任を持ちます。
  • org.relaxng.datatype.DatatypeLibraryインターフェースを実装するクラス: このクラスは、与えられた型のローカル名から、正しいデータ型のクラスをロードする責任を持ちます。
  • org.relaxng.datatype.DatatypeLibraryFactoryインターフェースを実装するファクトリ・クラス: このクラスは、与えられたライブラリの名前空間URIから、正しいデータ型ライブラリをロードする責任を持ちます。

では、それぞれのクラスを説明しましょう。

素数DatatypeLibraryFactory

デバッグのヒント

ライブラリがうまく動作しない場合には、ライブラリが発見されロードされていることを確認するために、このメソッドの最初でSystem.out.printlnを呼んでみます。

このファクトリ・クラスは簡単です。これにはcreateDatatypeLibrary という一つのメソッドがあり、このメソッドは引き数としてタイプ・ライブラリの名前空間URIを受け取り、そのライブラリのインスタンスを返します。名前空間が、このライブラリの名前空間と一致しない場合には、このメソッドは単に null を返し、RELAX NGバリデータは正しいライブラリを求めて他を探します。リスト1のコードは、これを示しています。


リスト1. DatatypeLibraryFactoryの実装
                
package com.elharo.xml.relaxng;

import org.relaxng.datatype.*;

public class PrimeDatatypeLibraryFactory 
 implements DatatypeLibraryFactory {

  public final static String namespace 
    = "http://ns.cafeconleche.org/relaxng/primes";

  public DatatypeLibrary createDatatypeLibrary(String namespace) {
    if (PrimeDatatypeLibraryFactory.namespace.equals(namespace)) {
      return new PrimeDatatypeLibrary();
    }
    return null;
  }

}


RELAX NGは、JavaサービスAPIを使ってファクトリを検索します。org.relaxng.datatype.DatatypeLibraryFactory という単純テキスト・ファイルをクラス・パスにあるディレクトリか、META-INF/services/ ディレクトリのいずれかのJARファイルに追加します。このファイルは、(各行に一つずつ)JARでまとめられたファクトリ・クラスの完全なパッケージ限定名を含んでいます。この場合は、単にcom.elharo.xml.relaxng.PrimeDatatypeLibraryFactory です。


素数DatatypeLibrary

DatatypeLibrary実装クラスは、ほんの少しだけ複雑です(リスト2参照)。名前空間毎に一つのDatatypeLibraryがありますが、これは2つのメソッドを必要とします。createDatatypeメソッドは、例えば「prime」などのローカル名を取り、そしてそれに適切なデータ型オブジェクトを返します。この単純な例では一つの型しかありませんが、大部分のライブラリでは、同じ名前空間に複数の単純型を用意しています。createDatatypeBuilderメソッドは、org.relaxng.datatype.DatatypeBuilderオブジェクトを返します。より複雑なタイプ・ライブラリでは、文脈(context)や、スコープ内の基底URIおよび名前空間接頭辞などのようなパラメータに依存する型を生成するために、このメソッドを使うこともできます。しかし、この例では文脈を必要としないので、createDatatypeBuilderは、単に素数データ型で設定されたorg.relaxng.datatype.helpers.ParameterlessDatatypeBuilder のインスタンスを返します。


リスト2. DatatypeLibraryの実装
                
package com.elharo.xml.relaxng;

import org.relaxng.datatype.*;
import org.relaxng.datatype.helpers.*;

public class PrimeDatatypeLibrary implements DatatypeLibrary {

  public Datatype createDatatype(String typeLocalName)
   throws DatatypeException {
    if ("prime".equals(typeLocalName)) {
      return new PrimeDatatype();
    }
    throw new DatatypeException("Unsupported type: " + typeLocalName);
  }

  public DatatypeBuilder createDatatypeBuilder(String baseTypeLocalName) 
   throws DatatypeException {
    return new ParameterlessDatatypeBuilder(
     createDatatype("prime")
    );
  }

}


素数データ型

パブリックAPIの最後はデータ型自身、つまりorg.relaxng.datatype.Datatypeインターフェースのインスタンスです。このデータ型は、書かなければならない最も複雑なクラスです。このクラスは、下記のような幾つかの責任を負っています。

  • 与えられた文字列が、そのデータ型の有効なインスタンスかどうかを判定する。
  • そのデータ型のインスタンスを表すオブジェクトを作る。
  • データ型の意味に照らして、2つのオブジェクトが同一かどうかを比較する。
  • こうしたオブジェクトに対するハッシュ・コードを算出する。
  • その型に対するストリーミング・バリデータを提供する。
  • その型がID型かどうかを判定する。
  • その型が文脈依存かどうかを判定する。

これらの責任を、それぞれ順番に説明しましょう。

妥当性の確認

リスト3のコードが示すように、2つのメソッドが文字列の妥当性を検証します。もし文字列がそのデータ型の妥当なインスタンスであればisValidは、真(true) を返します。そうでなければ 偽(false) を返します。checkValidは、文字列が妥当でない時にDatatypeException をスローします。


リスト3. 与えられた文字列の妥当性を確認するメソッド
                
public boolean isValid(String literal, ValidationContext context) {
    return isPrime(literal);
  }

  public void checkValid(String literal, ValidationContext context) 
   throws DatatypeException {
    if (!isValid(literal, context)) {
      throw new DatatypeException(literal + " is not a prime number");
    }
  }

この2つのメソッドは、privateのisPrimeメソッドに依存しています。isPrimeメソッドは、リスト4に示すように素数か否かの検証に、単純な(そして非効率な)アルゴリズムを実装しています。2(最小の素数)と、入力の平方根の間にあるすべての整数で入力を割り算し、その余りがゼロであれば、その数は素数ではない、としています。もちろん、素数か否かの検証には、もっと効率の良いアルゴリズムもありますが、ここに挙げたものが一番理解しやすいでしょう。


リスト4. 数字が素数か否かを検証するアルゴリズム
                
private boolean isPrime(String literal) {
      
    try {
      int candidate = Integer.parseInt(literal); 
      if (candidate < 2) return false;
      double max = Math.sqrt(candidate);
      for (int i = 2; i <= max; i++) {
        if (candidate % i == 0) return false;
      }
      return true;
    }
    catch (NumberFormatException ex) {
      return false;      
    }
    
  }

このメソッドは、入力文字列が数字ではない場合にも 偽 を返します。

ストリーミング検証

一部のデータ型は、ここで想定している数字より多くの、生のテキストを含んでいることもあります。例えば、埋め込まれたチェックサムに対して検証される、Base64でエンコードされたMPEGを考えてみてください。このような極端な場合では、データのサイズがJava文字列の最大サイズを超えてしまうかも知れません。この場合バリデータは、Datatype に対して、すべてを一度にメモリに取り込まずに少しずつ入力を検証できる、DatatypeStreamingValidator を要求することができます。ただし、この例では文字列がそれほど大きくないので、Datatype は単にorg.relaxng.datatype.helpers.StreamingValidatorImpl クラスのインスタンスを返します。

public DatatypeStreamingValidator createStreamingValidator(
   ValidationContext context) {
    return new StreamingValidatorImpl(this, context);
  }

このクラスは、文字列で渡されたデータの全ビットを文字列に保存し、それから文字列全体の妥当性を検証します。もっと大きなデータの処理が必要な、より効率的な実装では、直接DatatypeStreamingValidatorインターフェースを実装することができます。

オブジェクト表現

Datatypeクラスは、型のオブジェクト表現を何らかの形で提供することで、バリデータが同一性の比較や、ハッシュ・コードの計算ができるようにする必要があります。この例では、java.lang.Integerクラスが使えます。他の場合では、java.lang.Stringか、あるいは、その目的専用に書かれたカスタムのクラスを使います。どの型を選んだとしても、PrimeDatatypecreateValueメソッドは、リテラル文字列をこうしたオブジェクトに変換します。これをリスト5に示します。


リスト5. リテラル文字列をオブジェクトに変換するコード
                
public Object createValue(String literal, ValidationContext context) {

    if (isPrime(literal)) {
        return Integer.valueOf(literal);
    }
    return null;
    
  }

ある状況では最適化の一つとして、それぞれの型に対してではなく、型の値それぞれに対して別々に一つのオブジェクトだけを作るという、フライ級デザイン・パターン(flyweight design pattern)を使うことがあるかも知れません。

このタイプのオブジェクトを使うには、DatatypesameValueメソッドとvalueHashCodeメソッドに対して適用するのが唯一の方法です。equalshashCode が通常お互いに整合性があるのと同様に、これらのメソッドも一貫性のあるものにしてください。この例では単純に、これらのメソッドがIntegerクラスのequalsメソッドとhashCodeメソッドに依存するようにしました。これをリスト6に示します。


リスト6.sameValueメソッドとvalueHashCodeメソッド
                
  public boolean sameValue(Object value1, Object value2) {
    if (value1 == null) return value2 == null;
    else return value1.equals(value2);
  }

  public int valueHashCode(Object value) {
    return value.hashCode();
  }

バリデータは、同じオブジェクトのcreateValueメソッドが作ったものではない、こうしたメソッドに対して、どんなオブジェクトも渡してはならないことになっています。もし渡してしまうと、こうしたメソッドは、この特定の実装では何らかの妥当な振る舞いをしようとしますが、一般的な場合この振る舞いは未定義なのです。

ID

getIdTypeメソッドは、そのタイプが何らかのID制約を表明しているものかどうかを判定します。ここでは4つの可能性があり、それぞれは、Datatypeクラスにある、名前付き定数で識別されます。

  • Datatype.ID_TYPE_ID
  • Datatype.ID_TYPE_IDREF
  • Datatype.ID_TYPE_IDREFS
  • Datatype.ID_TYPE_NULL

primeデータ型は、どのIDでもIDの参照でもないので、そのgetIdTypeメソッドは、Datatype.ID_TYPE_NULL を返します。

public int getIdType() {
    return ID_TYPE_NULL;
  }


文脈(Context)

最後のメソッドは、isContextDependent です。素数が有効かどうかは、文脈に依存しないので、このメソッドは単に 偽 を返します。

public boolean isContextDependent() {
    return false;
  }


タイプ・ライブラリをパッケージし、インストールし、そして使用する

タイプ・ライブラリが書けたら、バリデータが使えるようにパッケージします。DatatypeLibraryFactoryクラスの名前を含む、META-INF/services/org.relaxng.datatype.DatatypeLibraryFactoryファイルを含めるのを忘れないでください。この .jarファイルをバリデータのクラス・パスに追加し、そして通常行うのと同じようにバリデータを実行します。例えばリスト7に示すXML文書が、integers.xmlというファイルにあるとしましょう。


リスト7. integers.xmlファイル
                
<?xml version="1.0"?>
<numbers>
  <number>2</number>
  <number>3</number>
  <number>4</number>
  <number>5</number>
  <number>6</number>
</numbers>

今度は、リスト8に示すような、primeタイプ・ライブラリを参照するスキーマが、primes.rngというファイルの中に見つかったとしましょう。


リスト8. すべての整数が素数であることを要求するRELAX NGスキーマ
                
<?xml version="1.0"?>
<element name="numbers" xmlns="http://relaxng.org/ns/structure/1.0">
  <oneOrMore>
    <element name="number">
      <data type="prime"
            datatypeLibrary="http://ns.cafeconleche.org/relaxng/primes"/>
    </element>
  </oneOrMore>
</element>

リスト9で示すように、このスキーマはjavaインタープリタを使って実行することができます。


リスト9. カスタムのタイプ・ライブラリで検証する
                
$ java -cp primetype.jar:msv.jar com.sun.msv.driver.textui.Driver
 primes.rng integers.xml
start parsing a grammar.
validating integers.xml
Error at line:5, column:21 of file:///Users/elharo/integers.xml
  4 is not a prime number

Error at line:7, column:21 of file:///Users/elharo/integers.xml
  6 is not a prime number

the document is NOT valid.

大成功! バリデータは、4 と 6 が合成数(つまり非素数)である、と正しく判定しています。


まとめ

実行可能JARにご注意

カスタムのタイプ・ライブラリのJARだけではなく、バリデータのJARも直接クラス・パスに追加する必要があります。もし、「java -jar」 を使ってバリデータを実行可能JARとして扱うのであれば、バリデータはカスタムのタイプ・ライブラリを発見できず、次のようなエラー・メッセージが表示されるでしょう。

"http://ns.cafeconleche.org/relaxng/primes" is not a recognized data type vocabulary 6:75@file:///Users/elharo/primes.rngfailed to load a grammar.

これがすべてです。単純な一つの型にとどまらず、複数の型を含むライブラリを書くことができ、より複雑な妥当性検証の規則を持った型を定義することができるのです。しかも、どんなタイプ・ライブラリでも必要なのは、型を定義し、それをロードするためのファクトリを設定する少数のクラスだけです。ここでは、コマンドラインのユーザ・インターフェース(UI)を使った妥当性検証を説明しましたが、GUIツールを使って妥当性検証を行うこともできれば、Java API for RELAX Verifiers (JARV) や Java API for XML Processing (JAXP) 1.3 の妥当性検証パッケージを使って、皆さん自身のプログラムの中に妥当性検証を組み込んでしまうこともできます。タイプ・ライブラリは、サービスAPIを使って動的にロードされるので、皆さんのJavaコードは一切変更する必要がありません。単にタイプ・ライブラリのJARをクラス・パスに置き、スキーマの中で型を参照するのです。もはやW3Cの単純なデータ型に制限されることはありません。まさにどんな文字列であっても、任意の判定規則の集合に照らし合わせて妥当性の検証ができます。スキーマ言語に合わせてビジネス・ルールを調整する代わりに、ビジネス・ルールに合うようにタイプ・ライブラリを作り上げることが可能になったのです。



ダウンロード

内容ファイル名サイズダウンロード形式
Pluggable prime number datatype for RELAX NGx-custyp_primedatatype.zip30KBHTTP

ダウンロード形式について


参考文献

  • この記事で使用しているサンプル・コードを ダウンロードしてください。

  • RELAX NG home page には、RELAX NGのソフトウェアやライブラリ、チュートリアル、仕様書などがあります。

  • Kohsuke KawaguchiのMultischema validator をダウンロードしてください。

  • RELAX NG Pluggable Datatype Libraries specification を読んでください。

  • James ClarkのJing には、文字列内の括弧が対応していることを検証する、データ型の実装サンプルがあります。

  • JARVJAXP 1.3 の妥当性検証インターフェースを使って、RELAX NGの妥当性検証を皆さんのコードに組み込んでください。

  • Wikipediaで、Turing completeness に関して調べてください。

  • developerWorksのDeveloper Bookstore には、Elliotte Rusty Harold著によるEffective XMLThe XML Bible を含めて、XMLに関連した書籍が豊富に取り揃えられていますので、ぜひご覧ください。

  • developerWorksのXMLゾーン には、他にもXML関連の資料が豊富に用意されています。

  • XMLおよび関連技術においてIBM認証開発者になる方法については、こちら を参照してください。

著者について

Photo of Elliot Rusty Harold

Elliotte Rusty Haroldはニューオーリンズ出身であり、時たま、おいしいgumbo(オクラ入りのスープ)を食べに帰っています。ただし現在はニューヨークのブルックリン近郊のProspect Heightsに、妻のBethと猫のCharm(charmed quarkからとりました)とMarjorie(義理の母の名前からとりました)と一緒に住んでいます。彼はPolytechnic Universityのコンピューター・サイエンスの非常勤教授として、Java技術とオブジェクト指向プログラミングを教えています。彼のCafe au Lait Webサイトは、インターネット上で最も人気のある独立系Javaサイトの一つです。また、そこから派生したCafe con Lecheは、最も人気のあるXMLサイトの一つです。彼の最近の著作には『Java I/O, 2nd edition』があります。現在はXML処理用のXOM APIやJaxen XPathエンジン、Jesterテスト・カバレッジ・ツールなどに取り組んでいます。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology
ArticleID=240097
ArticleTitle=RELAX NGとカスタム・データ型ライブラリ
publish-date=11232004
author1-email=elharo@metalab.unc.edu
author1-email-cc=dwxed@us.ibm.com

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。