2013-04-21

Schematronを使えるようにしたメモ

そもそもスキマトロンとは何であるか(わかっているひとには不要)

なんか以前も似たようなことを書いたような気もするけれど。

XML は木構造をなす要素 element に属性 attribute またはテキストノードというテキストデータを貼り付けることができるデータ構造で、要素や属性にはまあ自由な命名ができる。自由というだけでは情報交換の当事者にとって適切な構造であることは確保されない。もうちょっといってしまうとデータと称する foo.xml にジャバスクリプトを忍ばせた XHTML なんかが入っていると、うっかりブラウザで開いたら何かやられてしまうかもしれない。まあそんなわけで構造を機械的に検証 validate する技術が作られてきた。

XML 登場当初からあったのは DTD である。というか、DTD は SGML にもとからあった構造記述機能であって、XML は SGML の一種であるという導入をしたので、使えるから使ったのである。何か根本的に間違っているわけではないが、DTD の構文それじたいは XML ではないので、取り扱いに不便であるし、名前空間に対応していないから今日的には困る。というわけで、今はもうさすがにあまり使われない。

ついで W3C が開発したのが W3C XML Schema である。単に XML schema というと構造を記述する言語という普通名詞になるし、口頭で「おおもじエクセメルスキーマ」というのも冗長なので、あえてファイルのサフィックスで XSD と僕は呼んでいる。で、それがどんなに素敵なものであるかというあたりは先人の書き残したものを読んでいただきたい。俺は素敵としか言ってないからな。

で、結局のところ、DTD に加えて名前空間とデータ型が扱えればよいというミニマリズムで作られたのが RELAX NG である。yacc を知っていれば、DTD や XSD の中核はこれらに似ている、いいかえれば有限状態オートマトンで記述できそうだなという気がしてくると思うが、同種の記号の列のための言語を拡張して木構造のための理論を作って必要最小限の機能が盛り込まれているんだという。プログラム言語では FORTRAN から Pascal/C あたりへの進化の過程で行われたことをデータ言語ではこの21世紀にやっているのかあ、と感慨はあるが、今は RNG の布教をしてるんじゃないのでこのへんにしておく。

これらは、トップダウンで「甲は乙丙丁からなる(丙丁は省略してもよい)。乙は子丑寅からなる(丑は繰り返してもよい)」といった調子で記述をするものである。しかし、それだけでは思うように書けないこともある。

スキマトロンはその隙間を補うもので、特定のコンテキストで特定のパターンが現れたときに誤りとするというような記述スタイルである。

仕様と処理系の入手


まあ何はともあれ  http://schematron.com/ に行っていただきたい。構造は変わるかもしれないがなんでもそこにある。仕様は ISO 19757-3:2006 になったので、すぐに消えてなくなることはないはずだ。

処理系はいろんなものがあるというが、あまりいろいろ依存するのは嫌なので http://www.schematron.com/implementation.html に落ちている http://www.schematron.com/tmp/iso-schematron-xslt1.zip を使った。これは、XSLT1 処理系さえあれば使える。そこらの Linux に GNOME が入っているならば、ほぼ間違いなく libxml2 が入っているはずで、おそらくは xmllint と xsltproc コマンドも apt とか yum とかで容易にインストールできるだろう。これだけでいいのだ。

zip を開くとばらばらと xslt (ファイル名 *.xsl)が入っている。このうち、iso_dsdl_include と iso_abstract_expand と iso_svrl_for_xslt1 を順にスキマトロン言語の XML にかけてやると、 xslt が出てくるので、これを検証すべき xml データにかけてやれば、 svrl 形式でメッセージやらなにやらが出力されるというのである。(ま、いちおう readme は読んであげてね)

ちなみに、iso_dsdl_include は libxml2 とは相性が悪いみたいで、要は動かない。動くように libxml2 を直してあげると作者が喜ぶと思うが私はそこまでようやらんので https://gist.github.com/etoyoda/5421069 でも見ていただきたい。

プログラム例

実際に意味のあることをする例をひとつ https://gist.github.com/etoyoda/5429212 に示す。
  • ルート要素は schema で名前空間は "http://purl.oclc.org/dsdl/schematron" である。
  • schema 直下に title 要素を置く。ここでは、ISO 19139 の規則のうち XSD では検証できないものを2つ検証するといっている。
  • schema の下にまた並んで ns 要素を置く。これは、以下の XPath における接頭辞と名前空間の対応付けをする。
  • schema の下にそのあとは pattern 要素が並ぶ。
  • pattern の中には rule 要素を置く。context 属性はルールが適用される場所を指定する XPath である。
  • rule の中には assert 要素を置く。test 属性を評価して偽になる場合に assert 要素の内容が表示されると思えばよい
以上が必要最低限であるが文法についてもうすこし蛇足すると
  • let 要素というのを pattern や rule の中に書くことができて、変数に値を設定することができる。まあ所詮は構文糖ではあるが、test の長い式を $name にできるとうれしい。
  • assert 要素のかわりに report 要素というのを指定できて、この場合は test が真なときに内容が表示される。
  • assert または report には次のような属性を指定できる
    • id 属性は処理結果に出力され、どのルールによってメッセージが出たかを逆探知するためのものである
    • flag 属性も処理結果に出力されるが、内容は一意でなくてよく、ルールの分類(必須、勧告、情報など)を示すのに使える。
    • fpi 属性はルールに URI などの識別子があるときに書くためのものだが、処理結果には出てこないので何の役に立つかはわからない。
  • pattern 要素は何の役に立つかというと、いくつかのルールをまとめて abstract="true" として、パラメタを与えて引用する(パラメタは $ で参照する)ことで一種のマクロのようなものにするためのものである。使ったことはない。
  • 例では pattern に xml:id 属性を与えているが、これは自作プリプロセッサで inlcude を機能させるためのものである。xml:id 属性はスキーマがなくても ID 型として定義されており、libxml2 もそのことを知っているので、引用側でフラグメント( ~.xml#fragment の型の URL の # 以降)が使えるのである。
  • メッセージに XPath 式を記述するには <value-of select="xpath"/> とする。
で、どんなテストかというと
  • CI_ResponsibleParty(連絡先)には人名、組織名、役職名(individualName, organisationName, positionName)のいずれかを記述しなければならない(こういうのは、XSD や RNG では全部任意にするしかないので記述しにくい)
  • MD_Metadata(メタデータのルート要素)の記述対象 hierarchyLevel が 'dataset' ならば(単に属性値を取り出すだけじゃなくて欠損時にデフォルト値を与えるために文字列処理の醜いトリックを使っている)、経緯度範囲 EX_GeographicBoundingBox または地理的領域の文章記述 EX_GeographicDescription がなければならない。
わざとだめなのを食わせると処理結果はこんなかんじになる。


<?xml version="1.0" standalone="yes"?>
<svrl:schematron-output
xmlns:svrl="http://purl.oclc.org/dsdl/svrl"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:schold="http://www.ascc.net/xml/schematron" xmlns:sch="http://www.ascc.net/xml/schematron"
xmlns:iso="http://purl.oclc.org/dsdl/schematron" xmlns:gmd="http://www.isotc211.org/2005/gmd"
title="Requirements not enforceable with XML Schema (Table A.1) of ISO/TS 19139:2006" schemaVersion="">
  <svrl:ns-prefix-in-attribute-values uri="http://www.isotc211.org/2005/gmd" prefix="gmd"/>
  <svrl:active-pattern id="iso19139taba1pat" name="iso19139taba1pat"/>
  <svrl:fired-rule context="gmd:MD_Metadata"/>
  <svrl:fired-rule context="gmd:CI_ResponsibleParty"/>
 <svrl:failed-assert
test="gmd:individualName or gmd:organisationName or gmd:positionName"
id="ISO19139.TA1.responsibleParty:"
flag="M-ISO"
location="/*[local-name()='MD_Metadata' and namespace-uri()='http://www.isotc211.org/2005/gmd']/*[local-name()='contact' and namespace-uri()='http://www.isotc211.org/2005/gmd']/*[local-name()='CI_ResponsibleParty' and namespace-uri()='http://www.isotc211.org/2005/gmd']">
    <svrl:text>count of (individualName + organisationName + positionName) &gt; 0</svrl:text>
  </svrl:failed-assert>
  <svrl:fired-rule context="gmd:CI_ResponsibleParty"/>
</svrl:schematron-output>

なお、assert のかわりに report を使うと svrl:failed-assert のかわりに svrl:successful-report が出てくる。いずれにしても単にメッセージを表示したり @id や @flag を使って集計したり深刻なエラーでだけ落ちるようにしたりすることが(たとえば xslt でも)できる。


0 件のコメント:

コメントを投稿