目次


XMLの論考: RELAX NGによる逆襲: 第2回

ツールと特殊な問題点

前回の記事では、RELAX NGのスキーマの構文と意味体系を全体的に見たわけですが、詳しく触れなった問題点もありますので、今回はそれらの点をまず取り上げましょう。

最初に触れておきたいのは、情報セット(infoset)の拡充です。DTDでもW3CのXML Schemaでも情報セットの拡充が可能ですが、RELAX NGにはその機能がありません。RELAX NGの開発に加わったJames Clark氏はこの点で強い意見を持っています(Clark氏はほかにも数多くの有名なXMLツールを開発しています)。要するに、情報セットの拡充は、XMLのインスタンス文書とスキーマの役割分担に反するという考え方です。言い換えれば、DTDとW3CのXML Schemaには欠陥があり、RELAX NGにはその欠陥がない、というのがClark氏の主張です。私自身は複雑な心境ですが、Clark氏の考え方に共感できる部分もあります。

ではここで、情報セットの問題の基本に立ち返ってみましょう。XMLインスタンスについては、中に含まれているデータのチェックが可能です。妥当性の検証なしでインスタンスを解析する場合は、属性や要素本体に記述されている値をそのまま抽出できます。役割分担を重視する観点からすれば、スキーマはインスタンスが妥当かどうかだけをチェックするべきであって、文書の中の実際の情報を変更するべきではありません。しかし、DTDやW3CのXML Schemaを使って妥当性を検証すると、そのような役割分担が崩れてしまいます。たとえば、次のDTDがあるとしましょう。

リスト1. curious.dtd
<!ELEMENT foo EMPTY>
<!ATTLIST foo bar CDATA "curious"
              baz CDATA #FIXED "curiouser">

また、次のXMLインスタンスがあるとします。

リスト2. curious.xml
<?xml version="1.0"?>
<!DOCTYPE foo SYSTEM "curious.dtd">
<foo/>

この文書から書き出される情報セットは、妥当性を検証しないパーサーの場合と、妥当性を検証するパーサーの場合とで異なります(訳注:「異なることがある」というのが正確でしょう。妥当性を検証しないXMLプロセッサが外部実体やデフォルトの属性値を得るためにDTDを処理することは禁止されていないからです)。妥当性を検証しないユーティリティーであるxmlcat と、妥当性を検証するユーティリティーである4xml とを比べてみましょう(どちらも読み込んだデータをコンソールに書き出します)。

リスト3.妥当性を検証するパーサーと妥当性を検証しないパーサーから書き出された情報セット
% ./xmlcat curious.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<foo></foo>
% 4xml -p curious.xml
<?xml version="1.0" encoding="utf-8"?>
<foo bar="curious" baz="curiouser"/>

W3CのXML Schemaの場合、default 属性とfixed 属性はいずれも、<xsd:attribute> タグと<xsd:element> タグの両方に対して同じ意味を持ちます。

デフォルトを記述する利点は、XMLインスタンスを最小化できるということです。ここでは、まさにその目的でデフォルト(要するに#FIXED 属性) を使っています。しかし、このような書き方には危険もあります。まず、ローカルのXML文書の内容がリモートのURIに依存する場合は、そのURIを偽物ですりかえるという悪質な行為によって、デバッグの悪夢におそわれる危険があります。また、解析時にネットワーク接続が常に切れないと言う前提を置くのも非常に危険です。

この点、RELAX NGは情報セットの拡充をしません。ただし、あえて私見を述べるとすれば、「RELAX NGは情報セットの拡充を全くしない」というClark氏の主張はやや言い過ぎではないでしょうか。結局のところ、要素や属性にデータ型を指定するだけでも、実際には値の内容を変更していることになるからです。たとえば、文字列としての「1.0」と浮動小数点数としての「1.0」は、XMLインスタンスではまったく同じ表記になりますが、それぞれの値の内容は異なります。

数量の記述

W3CのXML Schemaは、DTDやRELAX NGのスキーマに比べて、数量 (出現回数) の指定が容易です。たとえば、<bar> 要素内で<foo> 要素を5回から30回出現させたい場合、W3CのXML Schemaではその指定を直接的に宣言できます。

リスト4. W3CのXML Schemaでの数量の書き方
<xsd:element name="bar">
  <xsd:element name="foo" minOccurs="5" maxOccurs="30"/>
</xsd:element>

DTDでも同じことを指定できますが、あまりにも無様な書き方になってしまいます。

リスト5. DTDでの数量の書き方
<!ELEMENT bar
    (foo, foo, foo, foo, foo, foo?,foo?,foo?,foo?,foo?
     foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?
     foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?,foo?) >

RELAX NGの場合は、<cardinality> タグを明示的に指定して、次のような書き方をしたいところです (ただし、これは私が考案した仮の書き方です)。

リスト6. (仮の) RELAX NG 2.0での数量の書き方
<element name="bar" xmlns="http://relaxng.org/ns/structure/1.0>
  <cardinality min="5" max="30">
    <element name="foo"/>
  </cardinality>
</element>

ところが、現行バージョンのRELAX NGでは、数量の指定に使えるタグが、<zeroOrMore><oneOrMore><optional> しかありません。それでも、名前付きのパターンを使えば、ややこしい数量の記述をいくらか簡略化できます。たとえば、短縮構文では次のように書けます。

リスト7. (実際の) RELAX NG短縮構文での数量の書き方
start = element bar { fivefoo, upto25foo }
fivefoo = element foo { empty }, element foo { empty },
          element foo { empty }, element foo { empty },
          element foo { empty }
maybefoo = element foo { empty }?
upto25foo =
  fivefoo?, fivefoo?, fivefoo?, fivefoo?,
  maybefoo, maybefoo, maybefoo, maybefoo, maybefoo

もちろん、名前の付け方はほかにもいろいろあると思いますが、名前付きのパターンを繰り返して累乗をうまく利用すれば、大きな数にも最低限の対応はできます。

変換と妥当性検証

RELAX NGのスキーマを処理するいろいろなツールがすでに出回っています。Java言語で実装されているツールがほとんどですが、Python、C#、Visual Basicで書かれたツールやライブラリーもあります。意外なことに、Perl、Ruby、C/C++ で書かれたライブラリーは見たことがありません。開発者としては、このあたりが狙い目かもしれません。

RELAX NGアプリケーションの1つの大きな分野は妥当性検証ツールです。DTDやW3CのXML Schemaに対応した妥当性検証を行うパーサーが存在するのと同じように、RELAX NGについても、コマンド行パーサー、オンライン・パーサー、ライブラリー・パーサーが数多く出回っています。検証用ツールほど大きな分野ではないものの、スキーマ間の変換を実行するためのツールも存在しています。Sun社のRELAX NG Converterや、James Clark氏のtrangDTDinst は、RELAX NG (XML構文と短縮構文)、DTD、W3CのXML Schemaの間で変換を実行するためのツールです。私も次回の記事に合わせて、やや小ぶりなPythonツール (compact2xml.py) を開発する予定です。このツールを活用すれば、4Suitexvif からRELAX NG短縮構文を利用できるようになります(その2つの開発元は、この種のツールの組み込みに興味を示しています)。

この変換については、もう少し詳しく見ておきましょう。第1回では、RELAX NGがW3CのXML Schemaよりも優れている面を取り上げましたが、実際に厳密な変換を実行してみるとその点がよく分かります。たとえば、前回の記事では、短縮構文で記述した図書館利用者のスキーマを例として使いました。

リスト8. 図書館利用者の短縮構文
element patron {
  element name { text }   &
  element id-num { text } &
  element book {
    attribute isbn { text } |
    attribute title { text }
  }*
}

XML構文のスキーマについては、第1回をご覧ください(意味は同じですが、書き方が込み入っています)。trangを使って、このスキーマをW3CのXML Schemaに変換します(trangの変換結果はなかなかの出来ばえです)。trangでは、入力ファイルと出力ファイルの拡張子に基づいて、スキーマのタイプを判別します(スイッチによって上書きすることもできます)。

リスト9. RELAX NGからW3CのXML Schemaへの変換
% java -jar trang.jar patron.rnc patron.xsd
% cat patron.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified" version="1.0">
  <xsd:element name="patron">
    <xsd:complexType>
      <xsd:choice minOccurs="0" maxOccurs="unbounded">
        <xsd:element ref="name"/>
        <xsd:element ref="id-num"/>
        <xsd:element ref="book"/>
      </xsd:choice>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="name">
    <xsd:complexType mixed="true"/>
  </xsd:element>
  <xsd:element name="id-num">
    <xsd:complexType mixed="true"/>
  </xsd:element>
  <xsd:element name="book">
    <xsd:complexType>
      <xsd:attribute name="isbn"/>
      <xsd:attribute name="title"/>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

trangの名誉のために書いておきますが、このW3CのXML Schemaは、この場合の最善の変換結果だと思います。このRELAX NGのスキーマで妥当だと判断されるXMLインスタンスは、このW3CのXML Schemaでも必ず妥当だと判断されることになりますし、どちらのスキーマでも多くのエラーが排除されます。しかし、ここで問題になるのは、実際には妥当でないXMLインスタンスが、このW3CのXML Schemaでは妥当だと判断されるケースがあるということです。その一例を次に示します。

リスト10. W3CのXML Schemaによる検証の限界
% cat patron-i1.xml
<?xml version="1.0" encoding="UTF-8"?>
<patron>
  <book isbn="0-528-84460-X"/>
  <name>John Doe</name>    <!-- repeats name subelement -->
  <name>Second Name</name>
  <id-num>12345678</id-num>
  <book title="Why RELAX is Clever"/>
</patron>
% cat patron-i2.xml
<?xml version="1.0" encoding="UTF-8"?>
<patron>
  <name>John Doe</name>
  <id-num>12345678</id-num>
  <!-- Too many and too few attributes of book element -->
  <book title="Why RELAX is Clever" isbn="0-528-84460-X"/>
  <book/>
</patron>
% cat patron-i3.xml
<?xml version="1.0" encoding="UTF-8"?>
<patron/>        <!-- No required subelements -->

ここに挙げた3つの例は、妥当性の検証を間違ってパスしてしまいます。しかしそうではあっても、W3CのXML Schemaは、完全に不適格な要素/属性を含んだXMLインスタンスや、要素の入れ子構造が不適切なXMLインスタンスならきちんと排除してくれます(たとえば、兄弟関係にあるべき<book><name> の入れ子になっている場合などです)。

次に、妥当性検証ツールを具体的に見てみましょう。検証が失敗したときに、便利なエラー・メッセージを書き出すという点で、jing は優れています。PythonのXMLライブラリーである4Suiteも、xvifライブラリーの1つのバージョンを組み込んでおり、妥当性を検証できます(xvifはオンラインでも利用できます。詳しくは、参考文献をご覧ください)。しかし、両者のエラー・メッセージにはかなりの違いがあります。

リスト11. jingの検証エラー・メッセージ
% java -jar ../trang/jing.jar patron.rng patron-i3.xml
Error at URL "file:/.../patron-i3.xml",
line number 2: unfinished element
% java -jar ../trang/jing.jar patron.rng patron-i1.xml
Error at URL "file:/.../patron-i1.xml",
line number 5: element "name" not allowed in this context
リスト12. 4Suiteの検証エラー・メッセージ
% 4xml --rng=patron.rng patron-i1.xml
Traceback (most recent call last):
...
  File "/.../site-packages/Ft/Xml/_4xml.py", line 89, in Run
    raise RngInvalid(result)
Ft.Xml.Xvif.RngInvalid: Qname {None}name not exected
% 4xml --rng=patron.rng patron-i3.xml
Traceback (most recent call last):
...
Ft.Xml.Xvif.RngInvalid

もちろん、アプリケーションの観点からすれば、エラー・メッセージの違いよりも、ライブラリーを利用するプログラミング言語の選択のほうが重要な問題です。

コンパイル済みの妥当性検証パーサーを作るツール(訳注:コンパイラ・コンパイラ)

RELAX NG以外の分野でほとんど見かけないツールは、1つの種類のスキーマだけに対応した妥当性検証ツールです。RELAX NGのホーム・ページでその種のツールのリンクをたどってみると、BaliRelaxNGCC といったツールが見つかります。これらのフレームワークは、特定のRELAX NGスキーマに特化した妥当性検証コードを自動的に生成します。そのような特化型の検証ツールは、汎用型の検証ツールよりも処理が高速だと考えられます。この種のツールが可能なのは(少なくともW3CのXML Schemaに対応したツールよりもはるかに直接的な処理が可能なのは)、RELAX NGの設計がアルゴリズム的解析にしっかりと根ざしているからにほかなりません。

RELAX NG対応のXMLエディター

残念ながら、現在出回っているXMLエディターは、W3CのXML SchemaほどにはRELAX NGをサポートしていません。もちろん、一番広くサポートしているのはDTDです。しかし実際には、RELAX NGの妥当性検証の考え方は非常にシンプルなので、カスタマイズも簡単にできるはずだということを考えれば、こうした現状は残念としか言いようがありません。RELAX NGのスキーマに対応したカスタムXMLエディターを開発し、妥当性を保持しながら属性や要素を組み込んでいけるようになれば理想的です。

この点で1つの妥協案は、trangなどのツールでRELAX NGのスキーマをW3CのXML SchemaやDTDに類似変換してから、GUIベースのXMLエディターで利用するという方法です。しかし、これではあまり便利とは言えません。

もちろん、RELAX NGに対応したXMLエディターがないわけではありません。JavaテクノロジーをベースにしたXML Operatorというエディターが存在します(参考文献で紹介しているRELAX NGのホーム・ページをご覧ください)。私も少しいじってみました。確かに使い道はあるかもしれませんが、以前に検討したいくつかのXMLエディターに比べればかなり見劣りがします(XMLエディターの製品レビューについては、参考文献を参照してください)。XML Operatorが実装しているのはごくわずかな機能だけで、XML Spyのように多彩なツール群を用意しているわけでもなければ、oXygenのようにスマートな使い勝手を売物にしているわけでもありません。

次回の記事

第1回とこの第2回では、RELAX NGのほとんどの機能と、RELAX NGを処理するツールのあらましを見てきました。次の第3回 (最終回) では、RELAX NGのスキーマの中に外部スキーマを組み込み、各種スキーマの指定内容を選択的にマージする方法を簡単に取り上げてから、中心的なテーマとして、RELAX NGの短縮構文の詳細と、その短縮構文とXML構文との対応関係を見ていく予定です。


ダウンロード可能なリソース


関連トピック

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=241024
ArticleTitle=XMLの論考: RELAX NGによる逆襲: 第2回
publish-date=03262003