Zum Inhalt springen

RELAX NGとSchematronを組み合わせたTEI XMLスキーマの実装ガイド

:::message
人手で検証を行った後、AIが記事を執筆しました。
:::

はじめに

TEI(Text Encoding Initiative)XMLを編集する際、要素や属性の構造検証だけでなく、より複雑なビジネスルールの検証が必要になることがあります。本記事では、RELAX NG(RNG)とSchematronを組み合わせて、構造検証と内容検証の両方を実現する方法を、実際のプロジェクトで直面した課題を例に解説します。

解決したい課題

日本の古典文学テキストをTEI XMLで校訂する際、以下のような要求がありました:

  1. ID参照の動的検証: corresp属性で参照するIDが、実際に文書内のwitness要素に存在することを検証したい
  2. Oxygen XML Editorでの補完機能: 編集時にIDの候補を自動表示したい
  3. 複数ID参照のサポート: スペース区切りで複数のIDを指定可能にしたい
  4. 特定要素のみ参照を許可: 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を組み合わせることで:

  1. 構造検証と内容検証の分離: それぞれの得意分野を活かした設計が可能
  2. 動的な検証ルール: 文書の内容に基づいた柔軟な検証
  3. エディタ支援: Oxygen XML Editorなどでの高度な編集支援
  4. わかりやすいエラーメッセージ: 日本語でのカスタムメッセージ

特にTEI XMLのような複雑な構造を持つ文書の編集において、この組み合わせは非常に強力なツールとなります。

参考資料

この記事で紹介したスキーマの完全なコードは、実際のプロジェクトで使用されているものです。同様の課題を抱えている方の参考になれば幸いです。

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert