:::message
人手で検証を行った後、AIが記事を執筆しました。
:::
はじめに
TEI(Text Encoding Initiative)XMLを編集する際、要素や属性の構造検証だけでなく、より複雑なビジネスルールの検証が必要になることがあります。本記事では、RELAX NG(RNG)とSchematronを組み合わせて、構造検証と内容検証の両方を実現する方法を、実際のプロジェクトで直面した課題を例に解説します。
解決したい課題
日本の古典文学テキストをTEI XMLで校訂する際、以下のような要求がありました:
- 
ID参照の動的検証: corresp属性で参照するIDが、実際に文書内のwitness要素に存在することを検証したい
- Oxygen XML Editorでの補完機能: 編集時にIDの候補を自動表示したい
- 複数ID参照のサポート: スペース区切りで複数のIDを指定可能にしたい
- 
特定要素のみ参照を許可: witness要素のIDのみを参照可能とし、person要素のIDが含まれる場合はエラーにしたい
なぜRNG + Schematronなのか?
RELAX NGの得意分野
- 要素・属性の構造定義
- データ型の指定
- 基本的な内容モデルの定義
Schematronの得意分野
- XPathベースの複雑な検証ルール
- 文書内の相互参照チェック
- カスタムエラーメッセージの提供
この2つを組み合わせることで、構造と内容の両面から厳密な検証が可能になります。
実装例
1. 基本的なRNGスキーマ構造
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
         xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
         xmlns:sch="http://purl.oclc.org/dsdl/schematron"
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
         ns="http://www.tei-c.org/ns/1.0">
  <!-- Schematron名前空間宣言 -->
  <sch:ns prefix="tei" uri="http://www.tei-c.org/ns/1.0"/>
  <!-- ここにSchematronルールを埋め込む -->
  <start>
    <ref name="TEI"/>
  </start>
  <!-- RNGによる構造定義 -->
</grammar>
2. ID定義とanyURI型の活用
Oxygen XML Editorで自動補完を実現するために、anyURI型を使用します:
<!-- 証本リスト -->
<define name="listWit">
  <element name="listWit">
    <oneOrMore>
      <element name="witness">
        <attribute name="xml:id">
          <data type="ID"/>
        </attribute>
        <text/>
      </element>
    </oneOrMore>
  </element>
</define>
<!-- 底本読み -->
<define name="lem">
  <element name="lem">
    <attribute name="corresp">
      <a:documentation>
        証本への参照
        #付きのIDREF形式で内部参照
        Oxygenでは#付きのxml:id一覧が表示されます
      </a:documentation>
      <list>
        <oneOrMore>
          <data type="anyURI"/>
        </oneOrMore>
      </list>
    </attribute>
    <text/>
  </element>
</define>
ポイント:
- 
data type="ID"で一意性を保証
- 
data type="anyURI"で#付きの内部参照を許可
- 
list要素でスペース区切りの複数値を許可
3. Schematronによる高度な検証
<sch:pattern id="witness-references">
  <sch:title>Witness ID参照の検証</sch:title>
  <sch:rule context="tei:lem[@corresp]">
    <sch:let name="listWitIds" value="//tei:listWit/tei:witness/@xml:id"/>
    <sch:let name="listPersonIds" value="//tei:listPerson/tei:person/@xml:id"/>
    <sch:let name="correspTokens" value="tokenize(normalize-space(@corresp), '\s+')"/>
    <!-- witnessのみを参照すべき -->
    <sch:assert test="every $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listWitIds
                      )" role="error">
      corresp属性は証本(witness)のIDのみを参照すべきです。
      利用可能なwitness ID: #<sch:value-of select="string-join($listWitIds, ', #')"/>
    </sch:assert>
    <!-- person IDが含まれている場合のエラー -->
    <sch:report test="some $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listPersonIds
                      )" role="error">
      corresp属性に人物(person)のIDが含まれています。
      検出されたperson ID: <sch:value-of select="
        string-join(
          for $token in $correspTokens
          return if (starts-with($token, '#') and substring($token, 2) = $listPersonIds) 
                 then $token 
                 else (),
          ', '
        )
      "/>
    </sch:report>
  </sch:rule>
</sch:pattern>
ポイント:
- 
sch:letで変数を定義し、XPathで動的に値を取得
- 
tokenize()で複数ID参照をパース
- 
sch:assertで条件を満たさない場合にエラー
- 
sch:reportで条件を満たす場合にエラー
- 
role="error"でエラーレベルを指定(warning、info も可能)
4. 実際の使用例
<!-- XML文書での使用 -->
<?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://purl.oclc.org/dsdl/schematron"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
    <teiHeader>
        <listWit>
            <witness xml:id="aaa">証本A</witness>
            <witness xml:id="iii">証本I</witness>
        </listWit>
        <listPerson>
            <person xml:id="abc">
                <persName>人物ABC</persName>
            </person>
        </listPerson>
    </teiHeader>
    <text>
        <body>
            <app>
                <!-- 正しい例:witnessのみ参照 -->
                <lem corresp="#aaa #iii">本文</lem>
                <rdg corresp="#aaa">別の読み</rdg>
            </app>
            <app>
                <!-- エラー例:personを含む -->
                <lem corresp="#aaa #abc">本文</lem>
                <rdg>別の読み</rdg>
            </app>
        </body>
    </text>
</TEI>
実装時の注意点
1. XPath 2.0の構文
Schematron内のXPath式では、for式の構文に注意が必要です:
<!-- 正しい -->
let $invalid := (
  for $token in $correspTokens
  return 
    let $id := substring($token, 2)
    return if ($id = $validIds) then () else $token
)
<!-- エラーになる -->
let $invalid := for $token in $correspTokens
                let $id := substring($token, 2)
                return if ($id = $validIds) then () else $token
2. IDREF vs anyURI
- 
IDREF型: #を含めることができず、Oxygenでの補完が制限される
- 
anyURI型: #付きの値を許可し、Oxygenが自動的にID補完を提供
3. Schematronのrole属性
- 
role="error": 赤色のエラーマーカー
- 
role="warning": 黄色の警告マーカー
- 
role="info": 青色の情報マーカー
応用例
複雑な相互参照の検証
<sch:pattern id="cross-references">
  <!-- app要素には必ずlem要素が1つ必要 -->
  <sch:rule context="tei:app">
    <sch:assert test="count(tei:lem) = 1">
      app要素には必ず1つのlem要素が必要です
    </sch:assert>
  </sch:rule>
  <!-- rdg要素のcorrespはlem要素と重複不可 -->
  <sch:rule context="tei:rdg[@corresp]">
    <sch:let name="lemCorresp" value="../tei:lem/@corresp"/>
    <sch:assert test="not(@corresp = $lemCorresp)">
      rdg要素のcorrespはlem要素と異なる値にしてください
    </sch:assert>
  </sch:rule>
</sch:pattern>
条件付き必須属性
<sch:pattern id="conditional-attributes">
  <sch:rule context="tei:date[@when]">
    <!-- when属性がある場合、ISO形式でなければならない -->
    <sch:assert test="matches(@when, '^\d{4}-\d{2}-\d{2}$')">
      when属性はYYYY-MM-DD形式で指定してください
    </sch:assert>
  </sch:rule>
</sch:pattern>
まとめ
RELAX NGとSchematronを組み合わせることで:
- 構造検証と内容検証の分離: それぞれの得意分野を活かした設計が可能
- 動的な検証ルール: 文書の内容に基づいた柔軟な検証
- エディタ支援: Oxygen XML Editorなどでの高度な編集支援
- わかりやすいエラーメッセージ: 日本語でのカスタムメッセージ
特にTEI XMLのような複雑な構造を持つ文書の編集において、この組み合わせは非常に強力なツールとなります。
参考資料
- RELAX NG Compact Syntax Tutorial
- Schematron Quick Reference
- TEI Guidelines
- Oxygen XML Editor Documentation
この記事で紹介したスキーマの完全なコードは、実際のプロジェクトで使用されているものです。同様の課題を抱えている方の参考になれば幸いです。
 


 
    
Top comments (0)