どうもいつまでたってもDITAのスキーマはDTDが主流のようです.でもDTDではいつまでたっても属性値や要素の値を適切な値に強制することができません.そういうときに力を発揮してくれるのがSchematron(スキーマトロン)です.今まであまり作ったことがなかったのでサンプルを作ってみました.
対象として考えたのが単位値です.機械などのメンテナンスの文書を作る場合、どこどこのナットはこの強度で締めろとか、オイルは何CC入れろとかいろいろな単位が登場します.単位値は一般に、数値+単位の形式をしているのですけれども、これを適切にチェックするのは結構難しそうです.例えば
・数値は整数値も実数値もある.下手をするとマイナス値もある.また10-20℃のように範囲値の場合もある.
・単位はいろんな種類がある.大抵数値の後につくが、例えばpH値のような場合、単位が前につく場合もある.
しかしいろいろ考えているとたぶん出来なくなってしまうので、
・マイナス値、範囲値はなし
・単位はいろいろカスタマイズできるように固定的なものとしない
という前提で考えることにしました.いろいろ試して作ってみたのが次のようなスキーマトロンです.スキーマトロンにはいろんな規格がありますが、一番強力なISO Schematronにしました.長さや、重さやいろんな単位を入れるようにしてあるのがご覧いただけると思います.また、チェックには正規表現のパターンマッチングを使用し、単位値と単位をmatches()とreplace()を使って別々に取り出す工夫をしています.
[unit.sch]
<pattern xmlns="http://purl.oclc.org/dsdl/schematron"
abstract="true" id="validate-unit">
<let name="lengthUnit" value="('mm','cm','m','km','in','ft')"/>
<let name="areaUnit" value="('cm2','m2','in2','ft2')"/>
<let name="volumeUnit" value="('cm3','m3','l','ml','in3','ft3')"/>
<let name="weightUnit" value="('mg','g','kg','t','bl','oz')"/>
<let name="powerUnit" value="('N','dyn','kgf','lbf')"/>
<let name="timeUnit" value="('s','sec','min','h','ms')"/>
<let name="speedUnit" value="('m/s','ft/s','km/h')"/>
<let name="temparatureUnit" value="('C','F')"/>
<let name="resistanceUnit" value="('Ω','kΩ','MΩ')"/>
<let name="currentUnit" value="('A','mA')"/>
<let name="voltageUnit" value="('V','mV')"/>
<let name="ratioUnit" value="('%')"/>
<let name="validUnits" value="($lengthUnit, $areaUnit, $volumeUnit, $weightUnit, $powerUnit, $pressureUnit, $timeUnit, $speedUnit, $temparatureUnit, $resistanceUnit, $currentUnit, $voltageUnit, $rotationUnit, $ratioUnit)"/>
<rule context="$unitValue">
<let name="sepStr" value="'�'"/>
<let name="uvRegExp" value="'^([\d]+?\.{1}?[\d]+?|[\d]+?)([\D23]+)$'"/>
<let name="match" value="matches(.,$uvRegExp)"/>
<let name="valueAndUnit" value="replace(.,$uvRegExp,concat('$1',$sepStr,'$2'))"/>
<let name="vPart" value="substring-before($valueAndUnit,$sepStr)"/>
<let name="uPart" value="substring-after($valueAndUnit,$sepStr)"/>
<report test="not($match)">Wrong unit value format. It should be numeric value part plus unit part.</report>
<let name="uValid" value="$uPart = ($validUnits)"/>
<report test="not($uValid) and $match">Wrong unit format.</report>
</rule>
<rule context="$unit">
<let name="uValid" value="string(.) = ($validUnits)"/>
<report test="not($uValid)">Wrong unit format.</report>
</rule>
</pattern>
これは、abstract="true"となっているので、特定のコンテキスト(バリデーションする対象)にバインディングされていません.そこで次のようなスキーマトロンで実際バリデーションする要素に結びつけます.
[validate-concept-unitval.sch]
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"
xmlns:sqf="http://www.schematron-quickfix.com/validator/process">
<include href="unit.sch"/>
<pattern id="ph-unit" is-a="validate-unit">
<param name="unitValue" value="ph[string(@outputclass) eq 'uv']"/>
<param name="unit" value="ph[string(@outputclass) eq 'unit']"/>
</pattern>
</schema>
ここでは、(特殊化するのが厄介だったので)、<ph outputclass="uv">で単位値を、<ph outputclass="unit">で単位を記述するものとしてあります.
そしてこのバリデーションシナリオをチェックしてCtrl + Shift + Vとやると、以下のconceptでは"Document is valid"と出てくれます.
<concept id="concept_m4n_zxq_2z">
<title>Unit value test</title>
<conbody>
<p><ph outputclass="uv">0.456kg</ph></p>
<p><ph outputclass="unit">m2</ph></p>
</conbody>
</concept>
でもこれでは実際動いているのか不安なので、データをいじくると
<concept id="concept_m4n_zxq_2z">
<title>Unit value test</title>
<conbody>
<p><ph outputclass="uv">0.456kgZZZ</ph></p>
<p><ph outputclass="unit">m2</ph></p>
</conbody>
</concept>
ちゃんと"Wrong unit format"と出てくれました.メデタシメデタシ!
残念なのはこのスキーマトロンはoXygenでは動かすことが出来るのですが、XMetaLでは無理な点です.スキーマトロンはたぶん間違いなくXSLTスタイルシートに内部的に変換されて動作するのでしょう.このスキーマトロンはXPath 2.0をふんだんにつかっているので、XSLT 1.0しかサポートしないXMetaLでは動いてくれようがないのです.