XSD Inference (4)モデルグループとパーティクル
Introduction
第4回は内容モデルのうち、モデルグループとパーティクルの推測に関する話。モデルグループとパーティクルはどちらも内容モデルを表すのに使われるものだけど、complex contentの内容として、complexType, extension, restrictionの各要素の子として出現できるのがモデルグループで、内容グループ自身あるいはその子として出現しうるものがパーティクルで、これらは同じものではない(XML Schema Structuresの3.8と3.9の違い)。*1
サポートされる内容グループ
XSD Inferenceでサポートされる内容グループは、かなり限られている。
まず、出現回数は、0, 1, maxOccursの3種類に限られている(つまりDTDと同じレベル)。これが不便な制約だと感じる人はほとんどいないだろう。逆に、インスタンス上では12回しか出現しなかったからmaxOccurs=12だ、なんて推測をされても困る。
そして内容モデルだが、具体的には以下の2種類しかサポートされていない:
- xs:elementを含むxs:sequence
- xs:elementを含む、maxOccurs="unbound"であるxs:choiceを含むxs:sequence
これ以外は全てNGだ。具体的には
- xs:group ref="..."はサポートされていない
- xs:allはサポートされていない
- xs:anyはサポートされていない
- xs:choice(そのもの)はサポートされていない
そもそもXSD Inferenceでは、最初に挙げた2パターンしか生成されないのだから、それしかサポートされないわけだ。でも、何でこの2つ以外はサポートされないのだろうか?
- まず、xs:groupの参照を展開するのはかなり面倒な処理になる。また、missing sub componentsを含む妥当なグループ参照が存在しうるので、xs:groupの参照を含むモデルグループは、結果的に正しくないスキーマ コンポーネントになる可能性がある。
- 次に、xs:allをサポートしない理由は自明ではない(が、あえてサポートするほどの魅力に欠ける内容グループであることは確かだ)。一度出現した要素を追跡しなければならないし、xs:allを抜けた後で出現しなかった要素にminOccurs="0"を追加しなければならなくなる面倒はある。もしxs:allが維持できなくなった場合は、xs:choiceのxs:sequenceに置き換えることは可能だ。
- xs:anyのサポートは実質的には不可能だ。現在出現すべきパーティクルがxs:anyであるときに、新しい要素が出現したらどうなるか。xs:anyにはwildcardがあって、その要素が常にxs:anyによって受け入れられるわけではない。しかし、親がxs:sequenceであれば、それはxs:anyとして消化するしかなくなる。xs:elementをxs:anyの前に挿入すると、それ以前には存在しなかった要素なのでminOccurs="0"としなければならず、そうするとxs:anyのwildcardを変更して、その要素の名前空間を排除しなければならなくなってしまう(そうしないとunique particle attribution違反となってしまうため)。そうすると、以前にはxs:anyとして許容できていた内容が、許容されなくなってしまう可能性があるのである。そして、逆にxs:anyを拡張すると、今度は以前に推測していた要素との間で、unique particle attribution違反を起こす可能性がある。
- xs:choiceは、追跡するのが困難になる上(筆者はもう考えるのが面倒になった)、choice内に含まれるパーティクルとの間でunique particle attribution違反を起こす可能性が高くなる。
…というわけで、上記2つのパターンに絞ってサポートする方が現実的だ。(xs:choiceを含むxs:sequenceという書き方は、何となくやぼったく見えるが、なぜこうしたのかはちょっと分かっていない。)
パーティクル推測プロセス
推測の進め方は、ここから上記2パターンに分けられる。その方が簡単だし。基本的には、sequenceとしての推測が不可能になったときに、はじめてchoiceのsequenceに移行するというモデルに基づいていて、choiceのsequenceはいわば最終形態なのである。
maxOccurs="unbounded" であるchoiceのsequenceとなるパターンの場合、まずchoiceに含まれる先頭のelementから、QNameのマッチするものを探す(名前のマッチングについては後の説明も参照)。もし存在すれば、そのelementの定義が現在のXmlReader上のElementを許容するように拡張する(新しいものを定義することはできない; unique particle attribution違反)。もし存在しなければ、新しく追加する。
単純なsequenceの推測はより複雑だ。sequenceの内容は、順序通りに出現しなければならないので、「現在推測中のパーティクルの位置」も重要になる。また、補助的な引数として、「現在推測中のパーティクルは出現したか」を保持しておくことも必要になる(これを仮に「出現フラグ」と呼ぼう)。
まず、sequenceに含まれるelement(これ以外は許容されていないことに注意)の名前にマッチするものを、「先頭から現在推測中のパーティクルの位置マイナス1まで」探す(最初はこの探索が行われない)。もし同じQNameをもつ要素が出現してしまったら、この時点で、このsequenceは維持することができない*2。なぜなら、XML Schema Structures 3.8.6の制限により、同じQNameをもつelementがあるモデルグループの子孫として含まれている場合、その型は同一でなければならない、という制約があり、同じQNameをもつelementについて、もし結果的に違うelementが推測されてしまったら、このモデルグループは不正なスキーマ コンポーネントになってしまうからだ(InferenceはXmlReaderベースで行われるので、バックトラッキングができないことも問題になる*3)。
もしここまでで名前の一致するelementパーティクルが出現しなければ、「現在推測中のパーティクル」のQNameとelementを比較する。もし一致した場合、ここで「出現フラグ」がonになっていなかったら、「出現フラグ」をonにし、もしonになっていたら、maxOccursをunboundedにする。そして、その要素型が現在のXmlReader上のElementを許容するように拡張する。もしQNameが一致しなければ、ここで「出現フラグ」がonであれば、そのパーティクルは正常に推測されていたということなので、「出現フラグ」をoffにして、出現フラグがoffであれば、この最後のパーティクルは出現しなかったということになる。ここでの処理には2パターン考えられるが、おそらくMS.NETではminOccursを0にし、monoでは単純にsequenceとしてはもはや失敗したとみなしてchoiceに移行する。
elementパーティクルのマッチング
elementパーティクルを探索するとき、外部名前空間に属する要素はNameではなくRefNameを調べなければならないことに注意しなければならない。このことは、現在のモデルグループを含むcomplexTypeを含むXmlSchemaのTargetNamespaceが参照される、ということを意味している。
これがコンパイルされたXmlSchemaElementであれば、QualifiedNameプロパティを参照することができるが、推測中のパーティクルはコンパイルされていないので、QualifiedNameを使うことはできない。