RELAX NGでスキーマ記述

元になるDTD

DTDの作成で作ったDTDをRELAX NGで書換えてみることにした.元になるDTDとXMLは以下の通りである.

---index.dtd---
<!ELEMENT root (img,content+,p,ancher*)>
<!ATTLIST root title CDATA #REQUIRED>

<!ELEMENT img EMPTY>
<!ATTLIST img width  CDATA #REQUIRED
              height CDATA #REQUIRED
              src    CDATA #REQUIRED
              alt    CDATA #REQUIRED>

<!ELEMENT content (#PCDATA|ul)*>
<!ATTLIST content href CDATA #REQUIRED
                  name CDATA #REQUIRED>

<!ELEMENT ul (li+)>
<!ELEMENT li (#PCDATA|a)*>

<!ELEMENT a (#PCDATA)>
<!ATTLIST a href CDATA #REQUIRED>

<!ELEMENT p (#PCDATA|a)*>

<!ELEMENT ancher (banner)>
<!ATTLIST ancher href CDATA #REQUIRED>

<!ELEMENT banner EMPTY>
<!ATTLIST banner src CDATA #REQUIRED
                 alt CDATA #REQUIRED>
---end of index.dtd---

---index.xml---
<?xml version="1.0" encoding="utf-8"?>

<root title="眠る猫の頁">
  <img width="333" height="250" src="cat.jpg" alt=""/>

  <content href="whatsnew/index.html" name="新着">
    2003.2.2
    <ul>
      <li><a href="saijiki/index.html">歳時記</a>に2003.1.26から2003.2.1までを追加.</li>
      <li><a href="biboroku/index.html">備忘録</a>のUNIXに「文字列を数える」を追加.</li>
    </ul>
  </content>
  <content href="saijiki/index.html" name="歳時記">
    日々の出来事など.
  </content>
  <content href="ryokoki/index.html" name="旅行記">
    旅先での写真や駄文を.
  </content>
  <content href="hyoryuki/index.html" name="漂流記">
    システム開発への私見です.
  </content>
  <content href="biboroku/index.html" name="備忘録">
    教えてもらったことやできたことを忘れないうちに.
  </content>
  <content href="zatsuroku/index.html" name="雑録">
    その他雑多なものが色々.
  </content>
  <content href="links/index.html" name="リンク">
    友人・知合い,便利なソフトウェア,参考になるサイト等.
  </content>

  <p>「眠る猫の頁」にリンクを張りたい方は<a href="about.html">リンクについて</a>へ.</p>

  <ancher href="http://www.nakka.com/wwwc/">
    <banner src="http://www.nakka.com/wwwc/wwwc_meta.png" alt="WWWC META Check"/>
  </ancher>
  <ancher href="http://validator.w3.org/check?uri=http://www.dab.hi-ho.ne.jp/sasa/">
    <banner src="links/banner/valid-xhtml11.png" alt="Valid XHTML 1.1!"/>
  </ancher>
  <ancher href="http://jigsaw.w3.org/css-validator/validator?uri=http://www.dab.hi-ho.ne.jp/sasa/">
    <banner src="links/banner/vcss.png" alt="Valid CSS!" />
  </ancher>
</root>
---end of index.xml---
    

Jingのインストール

validatorとしてJingをインストールする.JingにはJAR形式とWin32 executableの形式の2種類がある.どっちでもいい.今回はJAR形式をダウンロード・インストールした.インストールは以下の手順で行う.

  1. 圧縮ファイルをダウンロードして
  2. 適当なディレクトリに保管し
  3. 必要ならパスの設定orエイリアスの設定をする

cygwinを使っているので以下のaliasを.bashrcに定義した.

alias jing='java -jar "${PROGRAMFILES}/trang/jing.jar"'
        

例えば,index.xmlをRELAX NGで書いたスキーマファイルindex.rngで検証する場合は,以下のようになる.$はコマンドプロンプトで,何も出力がなければ検証終了である.

$ jing index.rng index.xml
        

なお,インストールディレクトリがjingではなくtrangになっている理由とwin32-executableではなくJARを使った理由は後述する.

とりあえず書いてみた

わからない時は力技だ.DTDと見比べながら書いてみる.まずはルート要素から.

<!-- index.dtd -->
<!ELEMENT root (img,content+,p,ancher*)>
<!ATTLIST root title CDATA #REQUIRED>

<!-- index.rng -->
<?xml version="1.0" encoding="utf-8"?>
<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
  <attribute name="title">
    <text/>
  </attribute>

  <element name="img">
  </element>

  <oneOrMore>
    <element name="content">
    </element>
  </oneOrMore>

  <element name="p">
  </element>

  <zeroOrMore>
    <element name="ancher">
    </element>
  </zeroOrMore>
</element>
        

個々の要素を埋める.imgから.

<!-- index.dtd -->
<!ELEMENT img EMPTY>
<!ATTLIST img width  CDATA #REQUIRED
              height CDATA #REQUIRED
              src    CDATA #REQUIRED
              alt    CDATA #REQUIRED>

<!-- index.rng -->
<element name="img">
  <attribute name="width">
    <text/>
  </attribute>
  <attribute name="height">
    <text/>
  </attribute>
  <attribute name="src">
    <text/>
  </attribute>
  <attribute name="alt">
    <text/>
  </attribute>
  <empty/>
</element>
    

content要素はul要素と文字列の混合だ.

<!-- index.dtd -->
<!ELEMENT content (#PCDATA|ul)*>
<!ATTLIST content href CDATA #REQUIRED
                  name CDATA #REQUIRED>

<!-- index.rng -->
<element name="content">
  <attribute name="href">
    <text/>
  </attribute>
  <attribute name="name">
    <text/>
  </attribute>
  <mixed>
    <optional>
      <element name="ul">
      </element>
    </optional>
  </mixed>
</element>
    

こうして,できあがったのがこれ.

<!-- index.rng -->
<?xml version="1.0" encoding="utf-8"?>
<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
  <attribute name="title">
    <text/>
  </attribute>

  <element name="img">
    <attribute name="width">
      <text/>
    </attribute>
    <attribute name="height">
      <text/>
    </attribute>
    <attribute name="src">
      <text/>
    </attribute>
    <attribute name="alt">
      <text/>
    </attribute>
    <empty/>
  </element>

  <oneOrMore>
    <element name="content">
      <attribute name="href">
        <text/>
      </attribute>
      <attribute name="name">
        <text/>
      </attribute>
      <mixed>
        <optional>
          <element name="ul">
            <oneOrMore>
              <element name="li">
                <mixed>
                  <element name="a">
                    <attribute name="href">
                      <text/>
                    </attribute>
                    <text/>
                  </element>
                </mixed>
              </element>
            </oneOrMore>
          </element>
        </optional>
      </mixed>
    </element>
  </oneOrMore>

  <element name="p">
    <mixed>
      <element name="a">
        <attribute name="href">
          <text/>
        </attribute>
        <text/>
      </element>
    </mixed>
  </element>

  <zeroOrMore>
    <element name="ancher">
      <attribute name="href">
        <text/>
      </attribute>
      <element name="banner">
        <attribute name="src">
          <text/>
        </attribute>
        <attribute name="alt">
          <text/>
        </attribute>
        <empty/>
      </element>
    </element>
  </zeroOrMore>
</element>
<!-- end of index.rng -->
        

名前付きパターン

力技は,投入する労力に比べ得るものは少ない.名前付きパターンを使って見通しを良くしよう.そのためにgrammar要素とstart要素を導入する.要するにルート要素にgrammar,grammar直下に一つのstart要素を用意すれば名前付きパターンが使えるわけだ.

<!-- index.rng -->
<?xml version="1.0" encoding="utf-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"> <!-- ルート要素はgrammar -->
  <start> <!-- 直下にstart要素 -->
    <element name="root">
      <attribute name="title">
        <text/>
      </attribute>

      <ref name="img"/>

      <oneOrMore>
        <ref name="content"/>
      </oneOrMore>

      <ref name="p"/>

      <zeroOrMore>
        <ref name="ancher"/>
      </zeroOrMore>
    </element>
  </start>

  <define name="img">
    <element name="img">
      <attribute name="width">
        <text/>
      </attribute>
      <attribute name="height">
        <text/>
      </attribute>
      <attribute name="src">
        <text/>
      </attribute>
      <attribute name="alt">
        <text/>
      </attribute>
      <empty/>
    </element>
  </define>

  <define name="content">
    <element name="content">
      <attribute name="href">
        <text/>
      </attribute>
      <attribute name="name">
        <text/>
      </attribute>
      <mixed>
        <optional>
          <element name="ul">
            <oneOrMore>
              <element name="li">
                <mixed>
                  <ref name="a"/>
                </mixed>
              </element>
            </oneOrMore>
          </element>
        </optional>
      </mixed>
    </element>
  </define>

  <define name="p">
    <element name="p">
      <mixed>
        <ref name="a"/>
      </mixed>
    </element>
  </define>

  <define name="ancher">
    <element name="ancher">
      <attribute name="href">
        <text/>
      </attribute>
      <element name="banner">
        <attribute name="src">
          <text/>
        </attribute>
        <attribute name="alt">
          <text/>
        </attribute>
        <empty/>
      </element>
    </element>
  </define>

  <define name="a">
    <element name="a">
      <attribute name="href">
        <text/>
      </attribute>
      <text/>
    </element>
  </define>
</grammar>
<!-- end of index.rng -->
        

データ型の指定

dataLibrary属性を使ってXML Schemaデータ型を使うことにする.imgタグのheight属性とwidth属性は正の整数にしてみる.

<?xml version="1.0" encoding="utf-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
  datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <!-- XML Schemaのデータ型を使用 -->
  <start>
    <element name="root">
      <attribute name="title">
        <text/>
      </attribute>
      <ref name="img"/>
      <oneOrMore>
        <ref name="content"/>
      </oneOrMore>
      <ref name="p"/>
      <zeroOrMore>
        <ref name="ancher"/>
      </zeroOrMore>
    </element>
  </start>

  <define name="img">
    <element name="img">
      <attribute name="width">
        <data type="positiveInteger"/> <!-- widthは正の整数 -->
      </attribute>
      <attribute name="height">
        <data type="positiveInteger"/>  <!-- heightは正の整数 -->
      </attribute>
      <attribute name="src">
        <text/>
      </attribute>
      <attribute name="alt">
        <text/>
      </attribute>
      <empty/>
    </element>
  </define>

[省略]

---end of index.rng---
        

試しにimgタグに小数や負数を指定してjingで検証を行うと,このようになる.

---index.xml---
<?xml version="1.0" encoding="utf-8"?>

<root title="眠る猫の頁">
<img width="333.5" height="-250" src="cat.jpg" alt=""/> <!-- widthに小数,heightに負数を指定 -->

[省略]
---end of index.xml---

$ jing index.rng index.xml
c:\xxx\index.xml:3: error: bad value for attribute "width"
c:\xxx\index.xml:3: error: bad value for attribute "height"
    

trangで変換

Trangを使うと,DTDからRELAX NGへ変換ができる.インストールはJing同様,

  1. 圧縮ファイルをダウンロードして
  2. 適当なディレクトリに保管し
  3. 必要ならパスの設定orエイリアスの設定をする

となる.ただし,TrangはJingと同じディレクトリにないといけない.ちなみに,Trangの圧縮ファイルの中にJingも入っている.前述したJingのインストールディレクトリがTrangになっているのは,Trangをダウンロードして一緒に入ってたJingを使っているためである.Jing同様,.bashrcに以下のaliasを定義した.

alias trang='java -jar "${PROGRAMFILES}/trang/trang.jar"'
        

冒頭のDTDindex.dtdからRELAX NGスキーマを作ってみる.ファイル名はtrang.rngとした.

$ trang index.dtd trang.rng
        

出力が何もなければ完了である.こんなファイルができる.

 <!-- trang.rng -->
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
  <define name="root">
    <element name="root">
      <ref name="attlist.root"/>
      <ref name="img"/>
      <oneOrMore>
        <ref name="content"/>
      </oneOrMore>
      <ref name="p"/>
      <zeroOrMore>
        <ref name="ancher"/>
      </zeroOrMore>
    </element>
  </define>
  <define name="attlist.root" combine="interleave">
    <attribute name="title"/>
  </define>
  <define name="img">
    <element name="img">
      <ref name="attlist.img"/>
      <empty/>
    </element>
  </define>
  <define name="attlist.img" combine="interleave">
    <attribute name="width"/>
    <attribute name="height"/>
    <attribute name="src"/>
    <attribute name="alt"/>
  </define>
  <define name="content">
    <element name="content">
      <ref name="attlist.content"/>
      <zeroOrMore>
        <choice>
          <text/>
          <ref name="ul"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>
  <define name="attlist.content" combine="interleave">
    <attribute name="href"/>
    <attribute name="name"/>
  </define>
  <define name="ul">
    <element name="ul">
      <ref name="attlist.ul"/>
      <oneOrMore>
        <ref name="li"/>
      </oneOrMore>
    </element>
  </define>
  <define name="attlist.ul" combine="interleave">
    <empty/>
  </define>
  <define name="li">
    <element name="li">
      <ref name="attlist.li"/>
      <zeroOrMore>
        <choice>
          <text/>
          <ref name="a"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>
  <define name="attlist.li" combine="interleave">
    <empty/>
  </define>
  <define name="a">
    <element name="a">
      <ref name="attlist.a"/>
      <text/>
    </element>
  </define>
  <define name="attlist.a" combine="interleave">
    <attribute name="href"/>
  </define>
  <define name="p">
    <element name="p">
      <ref name="attlist.p"/>
      <zeroOrMore>
        <choice>
          <text/>
          <ref name="a"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>
  <define name="attlist.p" combine="interleave">
    <empty/>
  </define>
  <define name="ancher">
    <element name="ancher">
      <ref name="attlist.ancher"/>
      <ref name="banner"/>
    </element>
  </define>
  <define name="attlist.ancher" combine="interleave">
    <attribute name="href"/>
  </define>
  <define name="banner">
    <element name="banner">
      <ref name="attlist.banner"/>
      <empty/>
    </element>
  </define>
  <define name="attlist.banner" combine="interleave">
    <attribute name="src"/>
    <attribute name="alt"/>
  </define>
  <start>
    <choice>
      <ref name="root"/>
    </choice>
  </start>
</grammar>
        

作成したスキーマに出てくるdefine要素のcombine属性にはこんな意味がある.RELAX NGは,同名の名前付きパターンを一つに合成する.combine="interleave"属性を指定した場合は順不同で合成する.したがって,以下の二つのスキーマは同じものになる.

<!-- define a -->
<define name="attlist.img" combine="interleave">
  <attribute name="width"/>
  <attribute name="height"/>
  <attribute name="src"/>
  <attribute name="alt"/>
</define>
        
<!-- define b -->
<define name="attlist.img" combine="interleave">
  <attribute name="src"/>
  <attribute name="alt"/>
</define>
<define name="attlist.img" combine="interleave">
  <attribute name="width"/>
  <attribute name="height"/>
</define>
        

今回例あげたtrang.rngの場合,同名の名前付きパターンはないので,combine="interleave"はあんまり関係はない.

参考