XMLSchema-01 Validateできるようになろう。

XML Schemaファイルを書くだけではつまらないし、よくわからないので、 なにはともあれ、ちゃんとValidateしてくれるプログラムを作っておこう。
Xerces 2.0.0を使った。

●とりあえず、XMLとスキーマファイル。
とりあえず、validateしてくれたらそれとわかるように、XML的には正しいけど、 スキーマ的には正しくないXMLファイルと、スキーマファイルを用意する。
test.xml

<?xml version='1.0' encoding='Shift_JIS'?> <hoge xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation='test.xsd'> <piyo/> <puyo/> </hoge>
test.xsd

<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="hoge"> <xsd:complexType> <xsd:sequence> <xsd:element name="piyo" type="xsd:string" /> <xsd:element name="peyo" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
スキーマ的には、「piyo要素の次はpeyo要素やで」と言っている。
でも、XMLの法は、piyo要素の次にはpuyo要素が来ているのでエラーになるはず。

●まずはXerces推奨形式
まずはXercesのドキュメント(Feature.html)に書いてあったサンプルを流用したバージョン。
import org.apache.xerces.parsers.DOMParser;

public class XerTest {

    public static void main(String[] args) {

        DOMParser parser = new DOMParser();

        try {
            parser.setFeature("http://xml.org/sax/features/validation", true);
            parser.setFeature("http://apache.org/xml/features/validation/schema", true);
        }
        catch (org.xml.sax.SAXException ex) {
            System.err.println("fail to set parser feature: " + ex.getMessage());
            return;
        }
        org.xml.sax.InputSource src = null;
        try {
            src = new org.xml.sax.InputSource(new java.io.FileReader("test.xml"));
        } catch (java.io.FileNotFoundException ex) {
            System.err.println("XML file not found.");
            return;
        }

        try {
            parser.parse(src);
        } catch ( org.xml.sax.SAXException e) {
            System.err.println("Parse failed: " + e.getMessage());
        } catch ( java.io.IOException ex ) {
            System.err.println("Parse failed: " + ex.getMessage());
        }
    }
}
try...catch ばっかりでいまいちわかりにくくなってしまったが、要は ということみたい。最初の、
parser.setFeature("http://xml.org/sax/features/validation", true);
では、validationをするということを指示している。 これをしないとXMLファイルとしての正当性を確認するだけで、validationはしない。
次の、
parser.setFeature("http://apache.org/xml/features/validation/schema", true);
では、文書定義ファイルがXMLスキーマだということを指示している。
これを指定しないと、文書定義ファイルをDTDだと解釈してぜんぜんだめになる。

というわけで、両Featureを指定するとめでたく、validation失敗のエラーになる。
[Error] :6:10: cvc-complex-type.2.4.a: Invalid content starting with element 'puyo'. 
The content must match '(("":piyo),("":peyo))'.

●SAX Parserファクトリ経由
上のバージョンはシンプルでいいんだけど、直接、Xercesパッケージ内のクラスでnewしているのが 今ひとつ気に入らない。javax.xml系と、org.xml系だけでなんとかならんだろうか。
というわけで、SAX Parserファクトリ経由バージョン。
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;

public class XerTest {

    public static void main(String strArgs[]) {

        SAXParserFactory spf = SAXParserFactory.newInstance();

        try {
            spf.setFeature("http://xml.org/sax/features/namespaces", true);
            spf.setFeature("http://xml.org/sax/features/validation", true);
            spf.setFeature("http://apache.org/xml/features/validation/schema", true);

        } catch ( org.xml.sax.SAXNotRecognizedException ex ) {
            ex.printStackTrace();
            return;
        } catch ( org.xml.sax.SAXNotSupportedException ex ) {
            ex.printStackTrace();
            return;
        } catch ( javax.xml.parsers.ParserConfigurationException ex ) {
            ex.printStackTrace();
            return;
        }

        SAXParser parser = null;
        try {
            parser =spf.newSAXParser();

        } catch ( org.xml.sax.SAXException ex ) {
            System.err.println("Parser creation failed: " + ex.getMessage());
            return;
        } catch (javax.xml.parsers.ParserConfigurationException ex ) {
            System.err.println("Parser creation failed: " + ex.getMessage());
            return;
        }

        try {
            parser.parse(new java.io.File("test.xml"), new TestHandler() );
        } catch ( Exception ex ) {
            System.err.println("Parse failed: " + ex.getMessage());
            return;
        }
    }
}
ますますtry...catchばっかり。実際にはもっとまとめてキャッチしたほうがみやすいんだが、 とりあえず今は実験中なのでこまめにキャッチ。
ここでのポイントは、 の3点。これで、陽にXercesのクラスを使わずにValidationできる。
先の例に比べて、セットするべきFeatureに、 "http://xml.org/sax/features/namespaces"が追加されているのに注意。
なんか、「ちゃんとnamespace処理してね」ということらしい。 これを指定しないとなんか「エレメントが変」とか言われて終わる。

spf.setFeature("http://xml.org/sax/features/namespaces", true);
spf.setFeature("http://xml.org/sax/features/validation", true);

の2行は、

spf.setNamespaceAware(true);
spf.setValidating(true);

でも同じ効果が得られる。Exceptionもはかないのでこっちのほうがいい気がする。
ただし、"http://apache.org/xml/features/validation/schema" だけは、setFeatureしないと いけないのでtry...catchペアが減るわけではないのだ。

ちなみに、各setFeatureは、SaxParserFactoryに対して実施しているが、 parser.getXMLReader() に対して実施しても同様な効果が得られる。

最後に注意するべきは、パースするときに指定するDefaultHandler。
実は、最初のうちは、
org.xml.sax.helpers.DefaultHandler
というクラスを見つけたので、 new org.xml.sax.helpers.DefaultHandler() とやっていたのだ。
これでひどい目にあった。このデフォルトハンドラはエラーがあっても何もしないのだ。

というわけで、以下のようなTestHandlerを作成することでvalidationのエラーも拾えるようになったのだ。
TestHandler.java

import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class TestHandler extends org.xml.sax.helpers.DefaultHandler { public void error (SAXParseException ex) throws SAXException { System.err.print("[error "); System.err.print(Integer.toString(ex.getLineNumber())); System.err.print(":"); System.err.print(Integer.toString(ex.getColumnNumber())); System.err.print("] "); System.err.println(ex.getMessage()); } }
他にもwarningとかいろいろオーバーライドできるものはあるが、とりあえずこれでよしとする。
で、実行結果。
[error 6:10] cvc-complex-type.2.4.a: Invalid content starting with element 'puyo'. 
The content must match '(("":piyo),("":peyo))'.
よしよし

●最後はDocumentBuilder経由
最後に、DocumentBuilderFactory経由のパターン。
普段はSAXなぞ使わないので、XMLをハンドリングするときにはもっぱらこの形式を使っていた。
でも、このやり方ではXML Schemaは扱えないものだとあきらめていたのだ。
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class XerTest {

   /**
    * 通常のXMLパーサー作成方式
    * DocumentBuilderFactoryにsetAttributeをする。
    */

    public static void main(String[] args) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setValidating(true);
        dbf.setNamespaceAware(true);

/*
        使用する文書定義ファイルがXMLSchemaであることを通告する。
        以下の2つの定数は、
        org.apache.xerces.jaxp.DocumentBuilderImpl の 
        JAXP_SCHEMA_LANGUAGE、W3C_XML_SCHEMAで、
        それぞれ定義されているが、publicじゃないので使えないのだ。
*/
        dbf.setAttribute(
            "http://java.sun.com/xml/jaxp/properties/schemaLanguage" ,
            "http://www.w3.org/2001/XMLSchema");

        DocumentBuilder db = null;
        try {
            db = dbf.newDocumentBuilder();
        } catch ( javax.xml.parsers.ParserConfigurationException ex ) {
            System.err.println("Fail to create DocumentBuilder: " + ex.getMessage());
            return;
        }

        // デフォルトだと警告がでるので一応ErrorHandlerを与えておく。

        db.setErrorHandler( new TestHandler() );

        org.w3c.dom.Document doc = null;
        try {
            doc = db.parse( new java.io.File("test.xml") );

        } catch (java.io.IOException ex) {
            System.err.println("Parse failed(io): " + ex.getMessage());
        } catch ( org.xml.sax.SAXException ex ) {
            System.err.println("Parse failed(sax): " + ex.getMessage());
        }
    }
}
作成した DocumentBuilderFactoryにsetAttributeで、「XMLスキーマを使うぞ」と、 通告できるのだ。
これがわからなくてXercesのソースを散々読んでしまった。

ちなみに、setAttributteでは、値がBooleanオブジェクトだと自動的にFeatureであると 認識されるそうなので、
dbf.setAttribute(
        "http://java.sun.com/xml/jaxp/properties/schemaLanguage" ,
        "http://www.w3.org/2001/XMLSchema");
の部分は、
dbf.setAttribute(
        "http://apache.org/xml/features/validation/schema" ,
        new Boolean(true));
としても同じように動く。
あとはエラーハンドラ。
別にセットしなくても動くのだが、
Warning: validation was turned on but an org.xml.sax.ErrorHandler was not
set, which is probably not what is desired.  Parser will use a default
ErrorHandler to print the first 10 errors.  Please call
the 'setErrorHandler' method to fix this.
と警告がでる。
というわけで、さっきのTestHandlerをそのまま与えている。 org.xml.sax.ErrorHanlderインターフェースをインプリすればいいので、わざわざ 他にもいろいろインプリしている org.xml.sax.helpers.DefaultHandler から派生する必要はないのだが、 org.xml.sax.ErrorHanlderを直接インプリすると、errorfatalErrorwarningを 3つとも実装しないといけなくなるので、楽ちんなTestHanlderを使ったのだ。

また、org.apache.xerces.util.DefaultErrorHandler をついつい使ってみたくなるのだが、 これは、org.apache.xerces.xni.parser.XMLErrorHandler というまったく違うインターフェースを 実装しているだけなのでぜんぜんだめ。

というわけで実行結果。
[error 6:10] cvc-complex-type.2.4.a: Invalid content starting with element 'puyo'. 
The content must match '(("":piyo),("":peyo))'.
よしよし


XML スキーマ ページへ戻る。