スキーマトロンを作ってみる.

どうもいつまでたってもDITAのスキーマDTDが主流のようです.でもDTDではいつまでたっても属性値や要素の値を適切な値に強制することができません.そういうときに力を発揮してくれるのがSchematron(スキーマトロン)です.今まであまり作ったことがなかったのでサンプルを作ってみました.

対象として考えたのが単位値です.機械などのメンテナンスの文書を作る場合、どこどこのナットはこの強度で締めろとか、オイルは何CC入れろとかいろいろな単位が登場します.単位値は一般に、数値+単位の形式をしているのですけれども、これを適切にチェックするのは結構難しそうです.例えば

・数値は整数値も実数値もある.下手をするとマイナス値もある.また10-20℃のように範囲値の場合もある.
・単位はいろんな種類がある.大抵数値の後につくが、例えばpH値のような場合、単位が前につく場合もある.

しかしいろいろ考えているとたぶん出来なくなってしまうので、

・マイナス値、範囲値はなし
・単位はいろいろカスタマイズできるように固定的なものとしない

という前提で考えることにしました.いろいろ試して作ってみたのが次のようなスキーマトロンです.スキーマトロンにはいろんな規格がありますが、一番強力なISO Schematronにしました.長さや、重さやいろんな単位を入れるようにしてあるのがご覧いただけると思います.また、チェックには正規表現のパターンマッチングを使用し、単位値と単位をmatches()とreplace()を使って別々に取り出す工夫をしています.

[unit.sch]
<?xml version="1.0" encoding="UTF-8"?>
    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="pressureUnit" value="('Pa','kPa','MPa','mmHg','mmH2O')"/>
    <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="rotationUnit" value="('rpm','rps')"/>
    <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="'&#xFFFD;'"/>
        <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]

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"
    <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">で単位を記述するものとしてあります.

実際にこのスキーマトロンを検証に使用するには、oXygenでは次のように検証シナリオにvalidate-concept-unitval.schをスキーマとして指定します.

イメージ 1


そしてこのバリデーションシナリオをチェックしてCtrl + Shift + Vとやると、以下のconceptでは"Document is valid"と出てくれます.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<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>

イメージ 2


でもこれでは実際動いているのか不安なので、データをいじくると

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<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"と出てくれました.メデタシメデタシ!

イメージ 3

残念なのはこのスキーマトロンはoXygenでは動かすことが出来るのですが、XMetaLでは無理な点です.スキーマトロンはたぶん間違いなくXSLTスタイルシートに内部的に変換されて動作するのでしょう.このスキーマトロンはXPath 2.0をふんだんにつかっているので、XSLT 1.0しかサポートしないXMetaLでは動いてくれようがないのです.

スキーマトロンは非常に便利で効果的なのですが日本ではあまり使われているような話を聞きません.もっとオーサリングを簡単で確実にし生産効率を上げるためにも活用されて良いのではないかな~と思います.