<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 中村覚</title>
    <description>The latest articles on DEV Community by 中村覚 (@_1cdfa8d07c5ded0d81c41).</description>
    <link>https://dev.to/_1cdfa8d07c5ded0d81c41</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3406706%2F68a8a3cd-bf84-442e-aedc-c033f8c21354.jpg</url>
      <title>DEV Community: 中村覚</title>
      <link>https://dev.to/_1cdfa8d07c5ded0d81c41</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_1cdfa8d07c5ded0d81c41"/>
    <language>en</language>
    <item>
      <title>Implementation Guide for TEI XML Schema Combining RELAX NG and Schematron</title>
      <dc:creator>中村覚</dc:creator>
      <pubDate>Fri, 01 Aug 2025 21:03:12 +0000</pubDate>
      <link>https://dev.to/_1cdfa8d07c5ded0d81c41/implementation-guide-for-tei-xml-schema-combining-relax-ng-and-schematron-4gfn</link>
      <guid>https://dev.to/_1cdfa8d07c5ded0d81c41/implementation-guide-for-tei-xml-schema-combining-relax-ng-and-schematron-4gfn</guid>
      <description>&lt;p&gt;:::message&lt;br&gt;
After manual verification, this article was written by AI.&lt;br&gt;
:::&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When editing TEI (Text Encoding Initiative) XML, validation of not only element and attribute structures but also more complex business rules becomes necessary. This article explains how to combine RELAX NG (RNG) and Schematron to achieve both structural and content validation, using real project challenges as examples.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenges to Solve
&lt;/h2&gt;

&lt;p&gt;When editing classical Japanese literary texts in TEI XML, we had the following requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic validation of ID references&lt;/strong&gt;: Validate that IDs referenced in &lt;code&gt;corresp&lt;/code&gt; attributes actually exist in &lt;code&gt;witness&lt;/code&gt; elements within the document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-completion in Oxygen XML Editor&lt;/strong&gt;: Automatically display ID candidates during editing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for multiple ID references&lt;/strong&gt;: Allow multiple IDs to be specified separated by spaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict references to specific elements&lt;/strong&gt;: Allow only &lt;code&gt;witness&lt;/code&gt; element IDs to be referenced, and generate an error if &lt;code&gt;person&lt;/code&gt; element IDs are included&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why RNG + Schematron?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  RELAX NG Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Element and attribute structure definition&lt;/li&gt;
&lt;li&gt;Data type specification&lt;/li&gt;
&lt;li&gt;Basic content model definition&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Schematron Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;XPath-based complex validation rules&lt;/li&gt;
&lt;li&gt;Cross-reference checking within documents&lt;/li&gt;
&lt;li&gt;Custom error message provision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining these two, we can achieve strict validation from both structural and content perspectives.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation Example
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Basic RNG Schema Structure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;grammar&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://relaxng.org/ns/structure/1.0"&lt;/span&gt;
         &lt;span class="na"&gt;xmlns:a=&lt;/span&gt;&lt;span class="s"&gt;"http://relaxng.org/ns/compatibility/annotations/1.0"&lt;/span&gt;
         &lt;span class="na"&gt;xmlns:sch=&lt;/span&gt;&lt;span class="s"&gt;"http://purl.oclc.org/dsdl/schematron"&lt;/span&gt;
         &lt;span class="na"&gt;datatypeLibrary=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-datatypes"&lt;/span&gt;
         &lt;span class="na"&gt;ns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Schematron namespace declaration --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:ns&lt;/span&gt; &lt;span class="na"&gt;prefix=&lt;/span&gt;&lt;span class="s"&gt;"tei"&lt;/span&gt; &lt;span class="na"&gt;uri=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Embed Schematron rules here --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;start&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ref&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"TEI"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/start&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- RNG structural definitions --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/grammar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. ID Definition and anyURI Type Usage
&lt;/h3&gt;

&lt;p&gt;To achieve auto-completion in Oxygen XML Editor, we use the &lt;code&gt;anyURI&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Witness list --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;define&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;oneOrMore&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"witness"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"xml:id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"ID"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/attribute&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;text/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/oneOrMore&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/define&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Base text reading --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;define&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"corresp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a:documentation&amp;gt;&lt;/span&gt;
        Reference to witness
        Internal reference in IDREF format with #
        Oxygen displays a list of xml:ids with #
      &lt;span class="nt"&gt;&amp;lt;/a:documentation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;oneOrMore&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"anyURI"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/oneOrMore&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/attribute&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/define&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data type="ID"&lt;/code&gt; ensures uniqueness&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data type="anyURI"&lt;/code&gt; allows internal references with &lt;code&gt;#&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt; element allows multiple space-separated values&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Advanced Validation with Schematron
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"witness-references"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:title&amp;gt;&lt;/span&gt;Witness ID Reference Validation&lt;span class="nt"&gt;&amp;lt;/sch:title&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:lem[@corresp]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWitIds"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"//tei:listWit/tei:witness/@xml:id"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listPersonIds"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"//tei:listPerson/tei:person/@xml:id"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"correspTokens"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"tokenize(normalize-space(@corresp), '\s+')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Should reference only witnesses --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"every $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listWitIds
                      )"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The corresp attribute should only reference witness IDs.
      Available witness IDs: #&lt;span class="nt"&gt;&amp;lt;sch:value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"string-join($listWitIds, ', #')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Error when person IDs are included --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:report&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"some $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listPersonIds
                      )"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The corresp attribute contains person IDs.
      Detected person IDs: &lt;span class="nt"&gt;&amp;lt;sch:value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"
        string-join(
          for $token in $correspTokens
          return if (starts-with($token, '#') and substring($token, 2) = $listPersonIds) 
                 then $token 
                 else (),
          ', '
        )
      "&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/sch:report&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define variables with &lt;code&gt;sch:let&lt;/code&gt; and dynamically retrieve values with XPath&lt;/li&gt;
&lt;li&gt;Parse multiple ID references with &lt;code&gt;tokenize()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Error when condition is not met with &lt;code&gt;sch:assert&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Error when condition is met with &lt;code&gt;sch:report&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Specify error level with &lt;code&gt;role="error"&lt;/code&gt; (warning, info also available)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Practical Usage Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Usage in XML document --&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://relaxng.org/ns/structure/1.0"?&amp;gt;
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://purl.oclc.org/dsdl/schematron"?&amp;gt;

&lt;span class="nt"&gt;&amp;lt;TEI&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;teiHeader&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;listWit&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;witness&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"aaa"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Witness A&lt;span class="nt"&gt;&amp;lt;/witness&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;witness&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"iii"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Witness I&lt;span class="nt"&gt;&amp;lt;/witness&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/listWit&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;listPerson&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;person&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;persName&amp;gt;&lt;/span&gt;Person ABC&lt;span class="nt"&gt;&amp;lt;/persName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/person&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/listPerson&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/teiHeader&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;app&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- Correct example: referencing only witnesses --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;lem&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa #iii"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Main text&lt;span class="nt"&gt;&amp;lt;/lem&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rdg&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Alternative reading&lt;span class="nt"&gt;&amp;lt;/rdg&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/app&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;app&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- Error example: includes person --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;lem&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa #abc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Main text&lt;span class="nt"&gt;&amp;lt;/lem&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rdg&amp;gt;&lt;/span&gt;Alternative reading&lt;span class="nt"&gt;&amp;lt;/rdg&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/app&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/TEI&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhainl7mxgge4s5hp10g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhainl7mxgge4s5hp10g6.png" width="800" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. XPath 2.0 Syntax
&lt;/h3&gt;

&lt;p&gt;Pay attention to the syntax of for expressions in XPath within Schematron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Correct --&amp;gt;&lt;/span&gt;
let $invalid := (
  for $token in $correspTokens
  return 
    let $id := substring($token, 2)
    return if ($id = $validIds) then () else $token
)

&lt;span class="c"&gt;&amp;lt;!-- Will cause error --&amp;gt;&lt;/span&gt;
let $invalid := for $token in $correspTokens
                let $id := substring($token, 2)
                return if ($id = $validIds) then () else $token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. IDREF vs anyURI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IDREF type&lt;/strong&gt;: Cannot include &lt;code&gt;#&lt;/code&gt;, limiting completion in Oxygen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;anyURI type&lt;/strong&gt;: Allows values with &lt;code&gt;#&lt;/code&gt;, Oxygen automatically provides ID completion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Schematron role Attribute
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;role="error"&lt;/code&gt;: Red error marker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="warning"&lt;/code&gt;: Yellow warning marker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="info"&lt;/code&gt;: Blue information marker&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complex Cross-Reference Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"cross-references"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- app element must have exactly one lem element --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"count(tei:lem) = 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The app element must have exactly one lem element
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- rdg element's corresp cannot duplicate lem element's --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:rdg[@corresp]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lemCorresp"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"../tei:lem/@corresp"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"not(@corresp = $lemCorresp)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The rdg element's corresp must have a different value from the lem element
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conditional Required Attributes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"conditional-attributes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:date[@when]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- If when attribute exists, it must be in ISO format --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"matches(@when, '^\d{4}-\d{2}-\d{2}$')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The when attribute must be specified in YYYY-MM-DD format
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;By combining RELAX NG and Schematron:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separation of structural and content validation&lt;/strong&gt;: Design that leverages each technology's strengths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic validation rules&lt;/strong&gt;: Flexible validation based on document content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor support&lt;/strong&gt;: Advanced editing support in Oxygen XML Editor and others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear error messages&lt;/strong&gt;: Custom messages in your native language&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Especially when editing documents with complex structures like TEI XML, this combination becomes an extremely powerful tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://relaxng.org/compact-tutorial-20030326.html" rel="noopener noreferrer"&gt;RELAX NG Compact Syntax Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://schematron.com/" rel="noopener noreferrer"&gt;Schematron Quick Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tei-c.org/guidelines/" rel="noopener noreferrer"&gt;TEI Guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.oxygenxml.com/doc/" rel="noopener noreferrer"&gt;Oxygen XML Editor Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;The complete schema code introduced in this article is actually used in production projects. I hope it will be helpful for those facing similar challenges.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>xml</category>
      <category>rng</category>
      <category>xpath</category>
      <category>tei</category>
    </item>
    <item>
      <title>RELAX NGとSchematronを組み合わせたTEI XMLスキーマの実装ガイド</title>
      <dc:creator>中村覚</dc:creator>
      <pubDate>Fri, 01 Aug 2025 20:59:52 +0000</pubDate>
      <link>https://dev.to/_1cdfa8d07c5ded0d81c41/relax-ngtoschematronwozu-mihe-wasetatei-xmlsukimanoshi-zhuang-gaido-2mjc</link>
      <guid>https://dev.to/_1cdfa8d07c5ded0d81c41/relax-ngtoschematronwozu-mihe-wasetatei-xmlsukimanoshi-zhuang-gaido-2mjc</guid>
      <description>&lt;p&gt;:::message&lt;br&gt;
人手で検証を行った後、AIが記事を執筆しました。&lt;br&gt;
:::&lt;/p&gt;
&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

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

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ID参照の動的検証&lt;/strong&gt;: &lt;code&gt;corresp&lt;/code&gt;属性で参照するIDが、実際に文書内の&lt;code&gt;witness&lt;/code&gt;要素に存在することを検証したい&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oxygen XML Editorでの補完機能&lt;/strong&gt;: 編集時にIDの候補を自動表示したい&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;複数ID参照のサポート&lt;/strong&gt;: スペース区切りで複数のIDを指定可能にしたい&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;特定要素のみ参照を許可&lt;/strong&gt;: &lt;code&gt;witness&lt;/code&gt;要素のIDのみを参照可能とし、&lt;code&gt;person&lt;/code&gt;要素のIDが含まれる場合はエラーにしたい&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  なぜRNG + Schematronなのか？
&lt;/h2&gt;
&lt;h3&gt;
  
  
  RELAX NGの得意分野
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;要素・属性の構造定義&lt;/li&gt;
&lt;li&gt;データ型の指定&lt;/li&gt;
&lt;li&gt;基本的な内容モデルの定義&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Schematronの得意分野
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;XPathベースの複雑な検証ルール&lt;/li&gt;
&lt;li&gt;文書内の相互参照チェック&lt;/li&gt;
&lt;li&gt;カスタムエラーメッセージの提供&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;この2つを組み合わせることで、構造と内容の両面から厳密な検証が可能になります。&lt;/p&gt;
&lt;h2&gt;
  
  
  実装例
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. 基本的なRNGスキーマ構造
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;grammar&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://relaxng.org/ns/structure/1.0"&lt;/span&gt;
         &lt;span class="na"&gt;xmlns:a=&lt;/span&gt;&lt;span class="s"&gt;"http://relaxng.org/ns/compatibility/annotations/1.0"&lt;/span&gt;
         &lt;span class="na"&gt;xmlns:sch=&lt;/span&gt;&lt;span class="s"&gt;"http://purl.oclc.org/dsdl/schematron"&lt;/span&gt;
         &lt;span class="na"&gt;datatypeLibrary=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-datatypes"&lt;/span&gt;
         &lt;span class="na"&gt;ns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Schematron名前空間宣言 --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:ns&lt;/span&gt; &lt;span class="na"&gt;prefix=&lt;/span&gt;&lt;span class="s"&gt;"tei"&lt;/span&gt; &lt;span class="na"&gt;uri=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- ここにSchematronルールを埋め込む --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;start&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ref&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"TEI"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/start&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- RNGによる構造定義 --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/grammar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. ID定義とanyURI型の活用
&lt;/h3&gt;

&lt;p&gt;Oxygen XML Editorで自動補完を実現するために、&lt;code&gt;anyURI&lt;/code&gt;型を使用します：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 証本リスト --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;define&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;oneOrMore&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"witness"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"xml:id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"ID"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/attribute&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;text/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/oneOrMore&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/define&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- 底本読み --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;define&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"corresp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a:documentation&amp;gt;&lt;/span&gt;
        証本への参照
        #付きのIDREF形式で内部参照
        Oxygenでは#付きのxml:id一覧が表示されます
      &lt;span class="nt"&gt;&amp;lt;/a:documentation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;list&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;oneOrMore&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"anyURI"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/oneOrMore&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/list&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/attribute&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/define&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ポイント&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data type="ID"&lt;/code&gt;で一意性を保証&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data type="anyURI"&lt;/code&gt;で&lt;code&gt;#&lt;/code&gt;付きの内部参照を許可&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt;要素でスペース区切りの複数値を許可&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Schematronによる高度な検証
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"witness-references"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:title&amp;gt;&lt;/span&gt;Witness ID参照の検証&lt;span class="nt"&gt;&amp;lt;/sch:title&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:lem[@corresp]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listWitIds"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"//tei:listWit/tei:witness/@xml:id"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"listPersonIds"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"//tei:listPerson/tei:person/@xml:id"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"correspTokens"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"tokenize(normalize-space(@corresp), '\s+')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- witnessのみを参照すべき --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"every $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listWitIds
                      )"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      corresp属性は証本（witness）のIDのみを参照すべきです。
      利用可能なwitness ID: #&lt;span class="nt"&gt;&amp;lt;sch:value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"string-join($listWitIds, ', #')"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- person IDが含まれている場合のエラー --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:report&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"some $token in $correspTokens 
                      satisfies (
                        starts-with($token, '#') and 
                        substring($token, 2) = $listPersonIds
                      )"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      corresp属性に人物（person）のIDが含まれています。
      検出されたperson ID: &lt;span class="nt"&gt;&amp;lt;sch:value-of&lt;/span&gt; &lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;"
        string-join(
          for $token in $correspTokens
          return if (starts-with($token, '#') and substring($token, 2) = $listPersonIds) 
                 then $token 
                 else (),
          ', '
        )
      "&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/sch:report&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ポイント&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sch:let&lt;/code&gt;で変数を定義し、XPathで動的に値を取得&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokenize()&lt;/code&gt;で複数ID参照をパース&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sch:assert&lt;/code&gt;で条件を満たさない場合にエラー&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sch:report&lt;/code&gt;で条件を満たす場合にエラー&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="error"&lt;/code&gt;でエラーレベルを指定（warning、info も可能）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. 実際の使用例
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- XML文書での使用 --&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://relaxng.org/ns/structure/1.0"?&amp;gt;
&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;?xml-model href="schema.rng" type="application/xml" 
    schematypens="http://purl.oclc.org/dsdl/schematron"?&amp;gt;

&lt;span class="nt"&gt;&amp;lt;TEI&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.tei-c.org/ns/1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;teiHeader&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;listWit&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;witness&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"aaa"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;証本A&lt;span class="nt"&gt;&amp;lt;/witness&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;witness&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"iii"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;証本I&lt;span class="nt"&gt;&amp;lt;/witness&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/listWit&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;listPerson&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;person&lt;/span&gt; &lt;span class="na"&gt;xml:id=&lt;/span&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;persName&amp;gt;&lt;/span&gt;人物ABC&lt;span class="nt"&gt;&amp;lt;/persName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/person&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/listPerson&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/teiHeader&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;app&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- 正しい例：witnessのみ参照 --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;lem&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa #iii"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;本文&lt;span class="nt"&gt;&amp;lt;/lem&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rdg&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;別の読み&lt;span class="nt"&gt;&amp;lt;/rdg&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/app&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;app&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- エラー例：personを含む --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;lem&lt;/span&gt; &lt;span class="na"&gt;corresp=&lt;/span&gt;&lt;span class="s"&gt;"#aaa #abc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;本文&lt;span class="nt"&gt;&amp;lt;/lem&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rdg&amp;gt;&lt;/span&gt;別の読み&lt;span class="nt"&gt;&amp;lt;/rdg&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/app&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/TEI&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhainl7mxgge4s5hp10g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhainl7mxgge4s5hp10g6.png" width="800" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  実装時の注意点
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. XPath 2.0の構文
&lt;/h3&gt;

&lt;p&gt;Schematron内のXPath式では、for式の構文に注意が必要です：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 正しい --&amp;gt;&lt;/span&gt;
let $invalid := (
  for $token in $correspTokens
  return 
    let $id := substring($token, 2)
    return if ($id = $validIds) then () else $token
)

&lt;span class="c"&gt;&amp;lt;!-- エラーになる --&amp;gt;&lt;/span&gt;
let $invalid := for $token in $correspTokens
                let $id := substring($token, 2)
                return if ($id = $validIds) then () else $token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. IDREF vs anyURI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IDREF型&lt;/strong&gt;: &lt;code&gt;#&lt;/code&gt;を含めることができず、Oxygenでの補完が制限される&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;anyURI型&lt;/strong&gt;: &lt;code&gt;#&lt;/code&gt;付きの値を許可し、Oxygenが自動的にID補完を提供&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Schematronのrole属性
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;role="error"&lt;/code&gt;: 赤色のエラーマーカー&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="warning"&lt;/code&gt;: 黄色の警告マーカー&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="info"&lt;/code&gt;: 青色の情報マーカー&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  応用例
&lt;/h2&gt;

&lt;h3&gt;
  
  
  複雑な相互参照の検証
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"cross-references"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- app要素には必ずlem要素が1つ必要 --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"count(tei:lem) = 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      app要素には必ず1つのlem要素が必要です
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- rdg要素のcorrespはlem要素と重複不可 --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:rdg[@corresp]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:let&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lemCorresp"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"../tei:lem/@corresp"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"not(@corresp = $lemCorresp)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      rdg要素のcorrespはlem要素と異なる値にしてください
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  条件付き必須属性
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sch:pattern&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"conditional-attributes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sch:rule&lt;/span&gt; &lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"tei:date[@when]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- when属性がある場合、ISO形式でなければならない --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sch:assert&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"matches(@when, '^\d{4}-\d{2}-\d{2}$')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      when属性はYYYY-MM-DD形式で指定してください
    &lt;span class="nt"&gt;&amp;lt;/sch:assert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sch:rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sch:pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;RELAX NGとSchematronを組み合わせることで：&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  参考資料
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://relaxng.org/compact-tutorial-20030326.html" rel="noopener noreferrer"&gt;RELAX NG Compact Syntax Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://schematron.com/" rel="noopener noreferrer"&gt;Schematron Quick Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tei-c.org/guidelines/" rel="noopener noreferrer"&gt;TEI Guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.oxygenxml.com/doc/" rel="noopener noreferrer"&gt;Oxygen XML Editor Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




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

</description>
      <category>xml</category>
      <category>rng</category>
      <category>xpath</category>
      <category>tei</category>
    </item>
  </channel>
</rss>
