STEP 7: elementRuleとhedgeRule再訪

$Id: step7.html 1.12 2000/02/22 07:06:08 murata Exp $

text by 村田 真

html by 難波 亮丞


STEP 6までのelementRule, hedgeRule の説明は,DTD を知っている人が すぐに理解できるように範囲を限定したものです.実際にはもっと一般化された 考え方がRELAXには存在しています。

1. elementRuleとラベル

elementRuleは、label 属性を持つことができます。ここ では、まずlabel属性の目的を説明し、次に機構を説明します。

1.1 出現位置によって異なる内容モデル

同じ名前のタグであっても出現位置によって内容モデルを変えたいという ことがしばしばあります。たとえば、章の中にある段落、表の中の段落は、どれも 段落には違いありません。しかし、段落の中に許されるものは微妙に異なりま す。たとえば、章にある段落は脚注を含んでもかまいませんが、表の中の段落 は文字だけに制限したいかもしれません。

<!-- This example is legal. -->
<section>
  <para>This paragraph can have footnotes <footnote>This 
        is a footnote</footnote>.</para>
</section>
<!-- This example is illegal. -->
<table>
  ...
  <para>This paragraph cannot have a footnote <footnote>This 
        is an illegal footnote</footnote>.</para>
  ...
</table>

したがって,段落が章の中にあるときと、表の中にあるときとでは、下記 の二つの内容モデルを使いわけたくなります。同様のことは、段落以外にも、 見出しや箇条書きなどに当てはまります。

<!-- Case 1: subordinate to <section> elements. -->
<!ELEMENT para (#PCDATA|footnote)*>

<!-- Case 2: subordinate to <table> elements. -->
<!ELEMENT para (#PCDATA)>

出現位置によって内容モデルを変えたいという例はHTMLにもあります。た とえば、aの入れ子の禁止、formの入れ子の禁止です。aの中にaが直接現れる ことは禁止されていますし、aの中にある他の要素の中にaが現れることも 禁止されています。formの場合も同様です。

<!-- This example is illegal. -->
<a href="foo"><span><a href="bar">dmy</a></span></a>
<!-- This example is also illegal. -->
<form>
  ...
  <div>
    <form>
      ...
    </form>
  </div>
  ...
</form>

aの中にはspan要素が出現できます.入れ子は間接的であっ ても禁止したいので、aの外にあるspanではaを許し、中にあるspanでは許さな いようにする必要があります。formについても同様で、formの外にあるdivでは formを許し、中にあるdivでは許さないようにする必要があります。

<!-- Case 1: subordinate to <a> elements. -->
<!ELEMENT span (#PCDATA|a)*>

<!-- Case 2: not subordinate to <a> elements. -->
<!ELEMENT span (#PCDATA)>

しかし、DTDでは、タグ名が同じである限り、内容モデルは常に一つです。 したがって、段落の出現箇所によって、内容モデルを変えることはできません。 spanの内容モデルも常に一つであり、spanがaの中にあるかどうかで内容モデ ルを変えることはできません。divの内容モデルも同様です。

この問題を回避するために、二つの方法が用いられてきました。一つの方 法は、出現箇所ごとに別のタグ名を導入することです。この方法を用いた例を 次に示します.章の中の段落にはparaInSectionというタグ名,表の中の段落 はparaInTableというタグ名を導入しています。

<!ELEMENT paraInSection (#PCDATA|footnote)*>

<!ELEMENT paraInTable (#PCDATA)>
<!-- This example is legal. -->
<section>
  <paraInSection>This paragraph can have footnotes <footnote>This 
        is a footnote</footnote>.</paraInSection>
</section>
<table>
  ...
  <paraInTable>This paragraph cannot have a footnote.</paraInTable>
  ...
</table>

この方法には、DTDが大きくなると、ほとんど同じタグ名が急速に増えると いう問題があります。段落,脚注,箇条書き等のような通常のタグ名が何倍に も増えるからです.

もう一つの方法は、タグ名は一つで済ませる代わりに、必要ないくつかの 内容モデルをすべて足した内容モデルを作るというものです。この方法を用い た例を次に示します.章の中の段落にも,表の中の段落にも,注釈が 許されています。

<!ELEMENT para (#PCDATA|footnote)*>

この方法には、検証が不十分になるという問題があります。いまの例だと, 以下の文書が検証を通ってしまいます.

<!-- This example is illegal. -->
<table>
  ...
  <para>This paragraph cannot have a footnote <footnote>This 
        is an illegal footnote</footnote>.</para>
  ...
</table>

1.2 elementRuleのlabel属性

同じタグ名が出現位置によって異なる内容モデルを持てるようにするため、 RELAXはラベルを導入しています.タグ名が同じであってもラベルが違えば, 異なるelementRuleが適用されます.

elementRuleはlabel 属性を持つことが出来ます. 次に、elementRuleの基本的な形を示します。

<elementRule pred="name" label="label-name">
  ...content model...
</elementRule>

label 属性が省略された場合は、pred 属性と同じ値を指定したものと 解釈されます。したがって、次の二つのelmentRule は等価です。

<elementRule pred="foo">
  ...content model...
</elementRule>
<elementRule pred="foo" label="foo">
  ...content model...
</elementRule>

脚注を含む段落と含まない段落とでラベルを使い分け,異なる内容モデル を指定した例を次に示します.

<elementRule pred="para" label="paraWithFNotes">
  <mixed>
    <ref label="footnote" occurs="*"/>
  </mixed>
</elementRule>

<elementRule pred="para" label="paraWithoutFNotes">
  <mixed>
    <empty/>
  <mixed/>
</elementRule>

<tag name="para"/>

一番目のelementRuleは、ラベルがparaWithFNotesである段落の内容は脚注 を含んだ文字列であることを示しています。二番目のelementRuleは、ラベル がparaWithoutFNotesである段落の内容は単なる文字列であることを示してい ます。

多くの場合にlabelとタグ名は一対一に対応します。実際、STEP 4までの例 ではそうでした。しかし、前節で示したような問題を扱うには、タグ名と一対 一に対応しないラベルが必要になります。

1.3 ref要素のlabel属性

次に、ref要素のlabel属性を説明します。この属性の値は、常にラベル名 です。STEP 1 では、値が要素型名だと書きました が、あの説明は正確ではありません。RELAX には要素型という概念はありませ ん。(実はXML 1.0 にもありません。要素型宣言は定義されていますが、要素 型の定義はどこを捜してもありません。)

前節の例にあるparaWithFNotesとparaWithoutFNotesはラベルですから、ref要素か ら参照することができます。章のための内容モデルからは、paraWithFNotesを 参照します。表(正確には表のセル)のための内容モデルからは、 paraWithoutFNotesを参照します。

<elementRule pred="section">
  <ref label="paraWithFNotes" occurs="*"/>
</elementRule>

<elementRule pred="cell">
  <ref label="paraWithoutFNotes" occurs="*"/>
</elementRule>

章の先頭の段落でだけ脚注を許さないということも可能です。最初に paraWithoutFNotesを参照し、それ以降ではparaWithFNotesを参照すれば よいのです。

<elementRule pred="section">
  <sequence>
    <ref label="paraWithoutFNotes"/>
    <ref label="paraWithFNotes" occurs="*"/>
  </sequence>
</elementRule>

2. ラベルを共有するelementRule, hedgeRule

複数のelementRule, hedgeRuleがlabel 属性に同一のラベルを指定して も構いません。また、複数のelementRuleのpred 属性が同一であっても構いま せん。

たとえば、次のモジュールでは,一番目から三番目のelementRuleのpred属 性はどれもfooですし,四番目と五番目のelementRuleのpred属性はどちらも foo1です.二番目,三番目のelementRuleのlabel属性はどちらもbar1ですし, それ以外のelementRule, hedgeRuleのlabel属性の値はすべてbarです.

<module
      moduleVersion="1.2"
      relaxCoreVersion="1.0"
      targetNamespace=""
      xmlns="http://www.xml.gr.jp/2000/relaxCore">

  <interface>
    <export labels="bar1"/>
  </interface>

  <elementRule pred="foo" label="bar">
    <empty/>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <ref label="bar" occurs="+"/>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <empty/>
  </elementRule>

  <elementRule pred="foo1" label="bar">
    <mixed>
      <empty/>
    </mixed>
  </elementRule>

  <elementRule pred="foo1" label="bar" type="integer"/>

  <hedgeRule label="bar">
    <empty/>
  </hedgeRule>

  <hedgeRule label="bar">
    <ref label="bar1"/>
  </hedgeRule>

  <tag name="foo"/>

  <tag name="foo1"/>

</module>

2.1 hedgeRuleの展開

hedgeRuleは最初にすべて展開されます。以下に、どのように展開される かを説明します。

(1) hedgeRuleがいくつか存在し、elementRuleは 存在しない場合

まず、あるラベルを持つhedgeRuleがいくつか存在し、同じラベルを持つ elementRuleが存在しない場合を考えます。このラベルを参照するref要素は、 これらすべてのhedgeRuleの内容モデルをまとめてchoice要素で括ったもので置き換 えられます。ref要素のoccurs属性は,choice要素によって引き継がれます。

次の例では、ラベルblockElemに対してhedgeRuleが二つ存在しています。 blockElemを持つelementRuleは存在しないものとします。

<hedgeRule label="blockElem">
  <ref label="para"/>
</hedgeRule>

<hedgeRule label="blockElem">
  <ref label="itemizedList"/>
</hedgeRule>

次のelementRuleの内容モデルは,ラベルblockElemを参照しています。

<elementRule pred="doc">
  <sequence>
    <ref label="title"/>
    <ref label="blockElem" occurs="*"/>
  </sequence>
</elementRule>

上の二つのhedgeRuleの内容モデルはどちらもref要素です。それらを まとめてchoice要素で括った結果は次のようになります。

<choice>
  <ref label="para"/>
  <ref label="itemizedList"/>
</choice>

最後に、展開後の結果を示します。<ref label="blockElem" occurs="*"/>が上のchoice要素で置き換えられ、occurs="*"がこのchoice要素に 引き継がれています。

<elementRule pred="doc">
  <sequence>
    <ref label="title"/>
    <choice occurs="*">
      <ref label="para"/>
      <ref label="itemizedList"/>
    </choice>
  </sequence>
</elementRule>

複数のhedgeRuleによるラベルの共有が認められているので,無理に一つ のhedgeRuleにまとめる必要がありません.たとえば, numberedItemizedListを追加しても,次のhedgeRuleを書き足すだけで済み ます.他を変更する必要はありません.

<hedgeRule label="blockElem">
  <ref label="numberedItemizedList"/>
</hedgeRule>

(2) 要素内容モデルを記述したhedgeRuleが いくつか存在し、elementRuleも存在する場合

つぎに、あるラベルを持つhedgeRuleがいくつか存在し、同じラベルを 持つelementRuleも存在する場合を考えます。

このラベルを参照するref要素は、choice要素によって置き換えられます. (1)のときと同様に,このラベルを持つすべてのhedgeRuleの内容モデルは, choice要素の子供要素になります.その他に,このref要素自身からoccurs属性 を取り除いたものも子供要素になります.ref要素のoccurs属性は,このchoice要 素によって引き継がれます。

次の例では、ラベルblockElemに対してhedgeRuleが二つ存在し、 blockElemを持つelementRuleも存在しています。

<hedgeRule label="blockElem">
  <ref label="para"/>
</hedgeRule>

<hedgeRule label="blockElem">
  <ref label="itemizedList"/>
</hedgeRule>

<elementRule pred="foo" label="blockElem" type="string"/>

二つのhedgeRuleの内容モデルとラベルblockElemを参照するrefとを まとめてchoice要素で括った結果は次のようになります。

<choice>
  <ref label="para"/>
  <ref label="itemizedList"/>
  <ref label="blockElem"/>
</choice>

blockElemを参照するelementRuleは(1)のときと同じだとします。以下に、 hedgeRuleの展開後の結果を示します。<ref label="blockElem" occurs="*"/>がchoice要素で置き換えられ、occurs="*"がこのchoice要素に転 記されます。

<elementRule pred="doc">
  <sequence>
    <ref label="title"/>
    <choice occurs="*">
      <ref label="para"/>
      <ref label="itemizedList"/>
      <ref label="blockElem"/>
    </choice>
  </sequence>
</elementRule>

前節で示したモジュール例を次にもう一度示します.この例 におけるhedgeRuleの展開を考えます.

<module
      moduleVersion="1.2"
      relaxCoreVersion="1.0"
      targetNamespace=""
      xmlns="http://www.xml.gr.jp/2000/relaxCore">

  <interface>
    <export labels="bar1"/>
  </interface>

  <elementRule pred="foo" label="bar">
    <empty/>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <ref label="bar" occurs="+"/>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <empty/>
  </elementRule>

  <elementRule pred="foo1" label="bar">
    <mixed>
      <empty/>
    </mixed>
  </elementRule>

  <elementRule pred="foo1" label="bar" type="integer"/>

  <hedgeRule label="bar">
    <empty/>
  </hedgeRule>

  <hedgeRule label="bar">
    <ref label="bar1"/>
  </hedgeRule>

  <tag name="foo"/>

  <tag name="foo1"/>

</module>

このモジュールにあるhedgeRuleはどちらもラベルbarを記述しています. その内容モデルは,<empty/>と<ref label="bar1"/>です. ラベルbarをlabel属性で指定するelementRuleも存在しています.したがって, まず次のchoice要素を構成します.

<choice>
  <ref label="bar"/>
  <empty/>
  <ref label="bar1"/>
</choice>

このchoice要素で,二番目の要素の内容モデルであるref要素を置き換えます. occurs="+"が指定されているので,choice要素に複写します.置き換えた後の 結果を次に示します.

<module
      moduleVersion="1.2"
      relaxCoreVersion="1.0"
      targetNamespace=""
      xmlns="http://www.xml.gr.jp/2000/relaxCore">

  <interface>
    <export labels="bar1"/>
  </interface>

  <elementRule pred="foo" label="bar">
    <empty/>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <choice occurs="+"/>
      <ref label="bar"/>
      <empty/>
      <ref label="bar1"/>
    </choice>
  </elementRule>

  <elementRule pred="foo" label="bar1">
    <empty/>
  </elementRule>

  <elementRule pred="foo1" label="bar">
    <mixed>
      <empty/>
    </mixed>
  </elementRule>

  <elementRule pred="foo1" label="bar" type="integer"/>

  <tag name="foo"/>

  <tag name="foo1"/>

</module>

2.2 elementRuleの適用

hedgeRuleを展開した後には、elementRuleだけが残ります。複数の elementRuleが同じラベルを共有することもありますし、同じパターンを共有 することもあります。最後に示したモジュールを例に,elementRuleが どのように適用されるのかを説明します

(1) ラベルが一意に決まり,適用されるelementRuleも一意に決まる例

まず,XML文書のどの要素についても適用されるelementRuleがた だ一つ定まり,ラベルもただ一つ定まる場合を説明します.

例として,次の簡単なXML文書を考えます.

<foo/>

タグ名はfooですから,適用できるelementRuleは一番目から三番目 のうちのいずれかです.

一番目のelementRuleの内容モデルは<empty>ですから,このfoo要素の 内容とマッチします.したがって,このelementRuleは適用できます.この elementRuleのlabel属性の値はbarです.しかし,先頭のexport要素のlabel属 性の値はbar1だけです.ラベルbarはルートとなるラベルとして許されていま せん.

<!-- bar is not allowed by the export element. -->
<foo/>  <!-- bar is computed by elementRule#1 -->

二番目のelementRuleの内容モデルは,空の内容とはマッチしません. したがって,このelementRuleは適用できません.

三番目のelementRuleの内容モデルは<empty>です.したがって, このelementRuleは適用できます.そのlabel属性の値はbar1であり,これはルートと なるラベルです.したがって,foo要素に振られるラベルはbar1であり, 適用されるelementRuleは三番目のものであることが分かります.

<!-- bar1 is allowed by the export element. -->
<foo/>  <!-- bar1 is computed by elementRule#3 -->

(2) ラベルが一意に決まらない例

次に,XML文書のある要素について,適用されるelementRuleがいくつか 存在し,いくつかのラベルが可能な場合を説明します.

例として,次のXML文書を考えます.

<foo>
  <foo/>
</foo>

ルートとなるラベルはbar1だけですから,ルートのfoo要素にはラベルbar1 を振る必要があります.このfoo要素の子供のfoo要素にはラベルbarとbar1の どちらを振るべきでしょうか.

子供foo要素は空要素ですから,一番目のelementRuleが適用でき,ラベル barを振ることができます.ラベルbarだけから成るラベル列は,二番目の elementRuleの内容モデルにマッチします.したがって,ルートのfoo 要素にラベルbar1を振ることができます.

<!-- bar1 is allowed by the export element. -->
<foo>   <!-- bar1 is computed by elementRule#2 -->
  <foo/> <!-- bar is computed by elementRule#1 -->
</foo>

一方,子供foo要素に三番目のelementRuleを適用し,ラベルbar1を振ることもで きます.ラベルbar1だけからなるラベル列は,二番目のelementRuleの内容モ デルにマッチします.したがって,ルートのfoo要素にラベルbar1を振ることが できます.

<!-- bar1 is allowed by the export element. -->
<foo>   <!-- bar1 is computed by elementRule#2 -->
  <foo/> <!-- bar1 is computed by elementRule#3 -->
</foo>

以上のことから,このXML文書は二通りに解釈できることが分かります.こ のように,RELAXでは複数の解釈が可能なことがあります.

(3) ラベルは一意に決まるが,elementRuleが一意に決まらない例

次に,XML文書のある要素について,適用されるelementRuleがいくつか 存在しますが,ラベルは一つに決まる場合を説明します.

例として,次のXML文書を考えます.

<foo>
  <foo1>a<foo1>
  <foo1>1<foo1>
</foo>

ルートとなるラベルはbar1だけですから,ルートのfoo要素には ラベルbar1を振る必要があります.この要素の内容は空では ありませんから,三番目のelementRuleは使えません.使えるのは 二番目のelementRuleです.したがって,二つのfoo1要素のラベルは barかbar1ということになります.

foo1をpred属性で指定しているelementRuleは,最後から 二番目のelementRuleと最後のelementRuleだけです.前者は, 混在内容モデルをもち,後者はデータ型integerを参照しています.

まず,一番目のfoo1要素について考えます.この要素に対して適用できる のは最後から二番目のelementRuleだけです.この要素の内容であるaは整数で はないので,最後のelementRuleは適用できません.したがって,このfoo1要 素のラベルはbarです.

次に,二番目のfoo1要素について考えます.一番目のfoo1要素と同様に, 最後から二番目のelementRuleは適用できます.この場合は,このfoo1要素の ラベルはbarです.いっぽう,1は整数ですから,最後のelementRuleも適用で きます.この場合も,このfoo1要素のラベルはbarです.したがって,どち らのelementRuleを適用しても,この文書は解釈可能です.

<!-- bar1 is allowed by the export element. -->
<foo>  <!-- bar1 is computed by elementRule#2 -->
  <foo1>a<foo1>  <!-- bar is computed by elementRule#4 -->
  <foo1>1<foo1>  <!-- bar is computed by elementRule#4 or 5 -->
</foo>

3. まとめ

いままで大規模なDTDを書くのに苦労してきた人には、STEP 7は魅力的に映 ると思います。必ず問題になる部分がRELAXでは綺麗に書けます。RELAX!

mura034@attglobal.net

Valid HTML 4.0!