SchematronによるXML検証

(2018.9 新規作成.)

埋め込みスキマトロンで, RELAX NG と組み合わせてみる。

Schematron

スキマトロン (Schematron) は, ルールベースの妥当性検証のためのスキーマ言語。W3C XML Schema または RELAX NGと組み合わせる。これらのスキーマに埋め込んで、補う形で使う。(埋め込みスキマトロン)

バージョンが二つある。XML名前空間URIで区別される.

バージョン XML namespace URI
ISO Schematron http://purl.oclc.org/dsdl/schematron
Schematron 1.5 Resource Directory (RDDL) for Schematron 1.5 http://www.ascc.net/xml/schematron

Schematron 1.5 は pattern タグに name属性がある。ISO Schematron にはない (廃止). ISO Schematron は diagnostics 要素がある。v1.5 にはない。上位互換というわけではない。

埋め込みでなく単独で使う場合の, スキマトロン自身のRELAX NGスキーマはこちら;

このページの一番下にリンクがある。分かりにくい。properties 要素が必須になっているが, 本当か?

応用例

応用例としては DocBook がある. OASIS 標準になっている; Standards | OASIS

DocBook v5.1 [Nov 2016] は ISO Schematronを使っていて, 一つ前の DocBook v5.0 [Nov 2009] では Schematron 1.5 が使われている.

Fedora 28 Linux には DocBook v5.0 が収録されている。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
[RAW]
  1. <schema xmlns="http://www.ascc.net/xml/schematron" >
  2. <!-- xmllint は pattern#name が必須 -->
  3. <pattern name="Check consistency of amounts">
  4. <rule context="order/items/item">
  5. <assert test="number(price) * number(qty) = number(amount)"
  6. >The amount doesn't match price * qty.
  7. </assert>
  8. <assert test="price/@currency = amount/@currency"
  9. >The currency in amount doesn't match price's.
  10. </assert>
  11. </rule>
  12. <rule context="order">
  13. <assert test="number(sum(items/item/amount)) = number(totalAmount)"
  14. >The totalAmount doesn't match the sum of amount.
  15. </assert>
  16. </rule>
  17. </pattern>
  18. </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
[RAW]
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <order>
  3. <items>
  4. <item>
  5. <qty>1</qty>
  6. <price currency="AUD">2300</price>
  7. <!-- 本当は、計算で出せる要素をわざわざ追加しない -->
  8. <amount currency="AUD">2300</amount>
  9. </item>
  10. <item>
  11. <qty>2</qty>
  12. <price currency="AUD">125</price>
  13. <amount currency="AUD">250</amount>
  14. </item>
  15. <item>
  16. <qty>2</qty>
  17. <price currency="AUD">75</price>
  18. <amount currency="AUD">150</amount>
  19. </item>
  20. </items>
  21. <totalAmount currency="AUD">2700</totalAmount>
  22. </order>

妥当ではないの。金額や通貨を誤った値にしてみた。検証に失敗するはず。

HTML/XML
[RAW]
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <order>
  3. <items>
  4. <item>
  5. <qty>1</qty>
  6. <price currency="AUD">2300</price>
  7. <!-- 本当は、計算で出せる要素をわざわざ追加しない -->
  8. <amount currency="AUD">2300</amount>
  9. </item>
  10. <item>
  11. <qty>2</qty>
  12. <price currency="AUD">125</price>
  13. <amount currency="AUD">2500</amount>
  14. </item>
  15. <item>
  16. <qty>2</qty>
  17. <price currency="AUD">75</price>
  18. <amount currency="USD">150</amount>
  19. </item>
  20. </items>
  21. <totalAmount currency="AUD">2700</totalAmount>
  22. </order>

jingxmllint に掛ける. どちらも, 妥当な文書は検証に成功し、妥当でないほうはきちんと失敗する.

$ 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
[RAW]
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- See /usr/share/xml/docbook5/schema/rng/5.0/docbook.rng -->
  3. <grammar xmlns="http://relaxng.org/ns/structure/1.0"
  4. datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
  5. xmlns:sch="http://purl.oclc.org/dsdl/schematron" >
  6. <start>
  7. <element name="order">
  8. <!-- xmllint は pattern#name が必須.
  9. ISO Schematron では, name 属性は廃止. -->
  10. <sch:pattern>
  11. <sch:rule context="order">
  12. <sch:assert test="number(sum(items/item/amount)) = number(totalAmount)"
  13. >The totalAmount doesn't match the sum of amount.
  14. </sch:assert>
  15. </sch:rule>
  16. </sch:pattern>
  17. <element name="items">
  18. <oneOrMore><ref name="item" /></oneOrMore>
  19. </element>
  20. <element name="totalAmount"><ref name="amount" /></element>
  21. </element>
  22. </start>
  23. <define name="item">
  24. <element name="item">
  25. <sch:pattern>
  26. <sch:rule context="order/items/item">
  27. <sch:assert test="number(price) * number(qty) = number(amount)"
  28. >The amount doesn't match price * qty.
  29. </sch:assert>
  30. <sch:assert test="price/@currency = amount/@currency"
  31. >The currency in amount doesn't match price's.
  32. </sch:assert>
  33. </sch:rule>
  34. </sch:pattern>
  35. <element name="qty"><data type="int" /></element>
  36. <element name="price"><ref name="amount" /></element>
  37. <element name="amount"><ref name="amount" /></element>
  38. </element>
  39. </define>
  40. <define name="amount">
  41. <attribute name="currency">
  42. <choice>
  43. <value>JPY</value>
  44. <value>USD</value>
  45. <value>AUD</value>
  46. </choice>
  47. </attribute>
  48. <data type="int" />
  49. </define>
  50. </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

  1. XSLT処理系で, スキマトロン部分を分離, 取り出す
  2. 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
[RAW]
  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron"
  3. xmlns:rng="http://relaxng.org/ns/structure/1.0"
  4. queryBinding="xslt2">
  5. <sch:pattern xmlns="http://relaxng.org/ns/structure/1.0">
  6. <sch:rule context="order">
  7. <sch:assert test="number(sum(items/item/amount)) = number(totalAmount)">The totalAmount doesn't match the sum of amount.
  8. </sch:assert>
  9. </sch:rule>
  10. </sch:pattern>
  11. <sch:pattern xmlns="http://relaxng.org/ns/structure/1.0">
  12. <sch:rule context="order/items/item">
  13. <sch:assert test="number(price) * number(qty) = number(amount)">The amount doesn't match price * qty.
  14. </sch:assert>
  15. <sch:assert test="price/@currency = amount/@currency">The currency in amount doesn't match price's.
  16. </sch:assert>
  17. </sch:rule>
  18. </sch:pattern>
  19. <sch:diagnostics/>
  20. </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.