(2018.9 新規作成.)
埋め込みスキマトロンで, RELAX NG と組み合わせてみる。
Schematron
スキマトロン (Schematron) は, ルールベースの妥当性検証のためのスキーマ言語。W3C XML Schema または RELAX NGと組み合わせる。これらのスキーマに埋め込んで、補う形で使う。(埋め込みスキマトロン)
ヴァージョンが二つある。XML名前空間URIで区別される.
Schematron 1.5 は pattern
タグに name
属性がある。ISO Schematron にはない (廃止). ISO Schematron は diagnostics
要素がある。v1.5 にはない。上位互換というわけではない。
2016年に ISO Schematron 2016 (2nd Edition) が発行された。
埋め込みでなく単独で使う場合の, スキマトロン自身のRELAX NGスキーマはこちら; schematron.com » The Schematron “Skeleton” Implementation
応用例
応用例としては DocBook がある. OASIS 標準になっている; DocBook Specifications
DocBook v5.1 [Nov 2016] は ISO Schematronを使っていて, 一つ前の DocBook v5.0 [Nov 2009] では Schematron 1.5 が使われている.
Fedora 28 Linux には DocBook v5.0 が収録されている。Fedora 36 には DocBook v5.1が収録。docbook5-schemas パッケージ.
スキーマファイルは、ここにある;
/usr/share/xml/docbook5/schema/rng/5.0/docbookxi.rng
/usr/share/xml/docbook5/schema/rng/5.0/docbook.rng
Schematron による検証
スキーマを簡単に書いてみる. Schematron 1.5 のサンプル.
HTML/XML
- <schema xmlns="http://www.ascc.net/xml/schematron" >
- <!-->
- <pattern name="Check consistency of amounts">
- <rule context="order/items/item">
- <assert test="number(price) * number(qty) = number(amount)"
- >The amount doesn't match price * qty.
- </assert>
- <assert test="price/@currency = amount/@currency"
- >The currency in amount doesn't match price's.
- </assert>
- </rule>
- <rule context="order">
- <assert test="number(sum(items/item/amount)) = number(totalAmount)"
- >The totalAmount doesn't match the sum of amount.
- </assert>
- </rule>
- </pattern>
- </schema>
スキマトロンは, 主要な要素は 6つだけ。他にも要素はあるが, この 6つさえ押さえておけば充分.
- <schema xmlns="http://purl.oclc.org/dsdl/schematron">
- スキマトロンのルート要素.
title
要素を含めてもよい. 複数の ns
要素, pattern
要素.
- <ns prefix="PPP" uri="UUU" />
- 書かないか, 一つ以上. XPath 式で使う名前空間のプレフィックス名を指定.
- <pattern>
- 一つ以上。複数の
rule
を含む
- <rule context="CCC">
- CCC がポイント。XPath 式でマッチした部分が、検証の対象になる.
assert
, report
を含む.
- <assert test="TTT">
- 満たすべき式を書く。これが成り立たなければ、検証に失敗.
- <report test="TTT">
- 報告.
次の文書を検証してみる. まずは妥当な文書.
HTML/XML
- <?xml version="1.0" encoding="UTF-8"?>
- <order>
- <items>
- <item>
- <qty>1</qty>
- <price currency="AUD">2300</price>
- <!-->
- <amount currency="AUD">2300</amount>
- </item>
- <item>
- <qty>2</qty>
- <price currency="AUD">125</price>
- <amount currency="AUD">250</amount>
- </item>
- <item>
- <qty>2</qty>
- <price currency="AUD">75</price>
- <amount currency="AUD">150</amount>
- </item>
- </items>
- <totalAmount currency="AUD">2700</totalAmount>
- </order>
妥当ではないの。金額や通貨を誤った値にしてみた。検証に失敗するはず。
HTML/XML
- <?xml version="1.0" encoding="UTF-8"?>
- <order>
- <items>
- <item>
- <qty>1</qty>
- <price currency="AUD">2300</price>
- <!-->
- <amount currency="AUD">2300</amount>
- </item>
- <item>
- <qty>2</qty>
- <price currency="AUD">125</price>
- <amount currency="AUD">2500</amount>
- </item>
- <item>
- <qty>2</qty>
- <price currency="AUD">75</price>
- <amount currency="USD">150</amount>
- </item>
- </items>
- <totalAmount currency="AUD">2700</totalAmount>
- </order>
jing や xmllint に掛ける. どちらも, 妥当な文書は検証に成功し、妥当でないほうはきちんと失敗する.
$ jing schema.schematron root.xml
error: assertion failed:
The totalAmount doesn't match the sum of amount.
error: assertion failed:
The amount doesn't match price * qty.
error: assertion failed:
The currency in amount doesn't match price's.
$ xmllint --schematron schema.schematron root.xml > /dev/null
Pattern: Check consistency of amounts
/order line 2: The totalAmount doesn't match the sum of amount.
/order/items/item[2] line 10: The amount doesn't match price * qty.
/order/items/item[3] line 15: The currency in amount doesn't match price's.
root.xml fails to validate
埋め込みスキマトロン (Embedded Schematron)
RELAX NG に埋め込む. こういうスキーマで, 検証できるようにする.
HTML/XML
- <?xml version="1.0" encoding="UTF-8"?>
- <!-->
- <grammar xmlns="http://relaxng.org/ns/structure/1.0"
- datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
- xmlns:sch="http://purl.oclc.org/dsdl/schematron" >
- <start>
- <element name="order">
- <!--
- >
- <sch:pattern>
- <sch:rule context="order">
- <sch:assert test="number(sum(items/item/amount)) = number(totalAmount)"
- >The totalAmount doesn't match the sum of amount.
- </sch:assert>
- </sch:rule>
- </sch:pattern>
- <element name="items">
- <oneOrMore><ref name="item" /></oneOrMore>
- </element>
- <element name="totalAmount"><ref name="amount" /></element>
- </element>
- </start>
-
- <define name="item">
- <element name="item">
- <sch:pattern>
- <sch:rule context="order/items/item">
- <sch:assert test="number(price) * number(qty) = number(amount)"
- >The amount doesn't match price * qty.
- </sch:assert>
- <sch:assert test="price/@currency = amount/@currency"
- >The currency in amount doesn't match price's.
- </sch:assert>
- </sch:rule>
- </sch:pattern>
- <element name="qty"><data type="int" /></element>
- <element name="price"><ref name="amount" /></element>
- <element name="amount"><ref name="amount" /></element>
- </element>
- </define>
- <define name="amount">
- <attribute name="currency">
- <choice>
- <value>JPY</value>
- <value>USD</value>
- <value>AUD</value>
- </choice>
- </attribute>
- <data type="int" />
- </define>
- </grammar>
処理系
そのまま解釈できる処理系が見当たらない。何ですと!
昔は MSV が RELAX NG + 埋め込み Schematron を解釈できていたが, Sun Microsystems が Oracle に買収され、Schematron add-on が公開されなくなった。新しい MSV はスキマトロンを扱えない。
"relames" というパッケージが Schematron add-on で, 2009.1 というバージョンが最後, か。
これが使えるかも? (試してない); http://www.topologi.com/resources/whitepapers.html にある, Schematron implementation in Java by Eddie Robertsson
jingコマンドは, RELAX NG と Schematron のそれぞれで検証できるが、埋め込みスキマトロンは対応していない。埋め込んだスキーマを渡しても, RELAX NG 部分だけで検証し、スキマトロン部分は単に無視する. 惜しい。
xmllint コマンドも同様. 加えて, xmllint は Schematron 1.5 のみ対応。ISO Schematron はパースに失敗する.
XSLT で抽出する
次のようにするしかない;
Combining Schematron with other XML Schema languages
- XSLT処理系で, スキマトロン部分を分離, 取り出す
- RELAX NG, Schematron それぞれで, 検証する
とはいえ, 実際にやってみると, わざわざ XSLT を使わなくていいんじゃないかと思う。一応、解説する.
XSLTスタイルシートは, ここにある ExtractSchFromRNG-2.xsl
(RELAX NG) ファイルか ExtractSchFromXSD-2.xsl
(W3C XML Schema) を使う.
XSLT プロセサとしては, xsltproc コマンド (libxsltパッケージ) や saxon コマンド (saxon-scripts パッケージ) が使える.
$ saxon -xsl:ExtractSchFromRNG-2.xsl -s:embedded.rng > extracted-by-saxon.schematron
Cannot find CatalogManager.properties
Warning: at xsl:transform on line 45 column 64 of ExtractSchFromRNG-2.xsl:
Running an XSLT 1 stylesheet with an XSLT 2 processor
$ xsltproc ExtractSchFromRNG-2.xsl embedded.rng > extracted.schematron
先ほどのRELAX NG埋め込みスキーマから, 次のように取り出せる;
HTML/XML
- <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron"
- xmlns:rng="http://relaxng.org/ns/structure/1.0"
- queryBinding="xslt2">
- <sch:pattern xmlns="http://relaxng.org/ns/structure/1.0">
- <sch:rule context="order">
- <sch:assert test="number(sum(items/item/amount)) = number(totalAmount)">The totalAmount doesn't match the sum of amount.
- </sch:assert>
- </sch:rule>
- </sch:pattern>
- <sch:pattern xmlns="http://relaxng.org/ns/structure/1.0">
- <sch:rule context="order/items/item">
- <sch:assert test="number(price) * number(qty) = number(amount)">The amount doesn't match price * qty.
- </sch:assert>
- <sch:assert test="price/@currency = amount/@currency">The currency in amount doesn't match price's.
- </sch:assert>
- </sch:rule>
- </sch:pattern>
- <sch:diagnostics/>
- </sch:schema>
ただ、これは, 検証できるようにスキーマを書いたため, こうなっているだけ。
埋め込む以上, RELAX NG の <element>
の構造を見て, Schematron の <rule context>
を調整してほしいが、XSLT による展開では単にコピーしてきてしまう。
XSLT シートを工夫したら何とかなるかもしれないが、そこまで試していない。
検証
extract したもので, jing で検証する。きちんと検出できている。
$ jing extracted-by-saxon.schematron root.xml
error: assertion failed:
The totalAmount doesn't match the sum of amount.
error: assertion failed:
The amount doesn't match price * qty.
error: assertion failed:
The currency in amount doesn't match price's.