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 ばっかりでいまいちわかりにくくなってしまったが、要は
- パーサーに、org.apache.xerces.parsers.DOMParser を使う。
- setFeature で必要なパーサーの振る舞いを決定する。
ということみたい。最初の、
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ばっかり。実際にはもっとまとめてキャッチしたほうがみやすいんだが、
とりあえず今は実験中なのでこまめにキャッチ。
ここでのポイントは、
- パーサーは、SAXParserFactory経由でSAXParserを作成する。
- SAXParserFactoryにsetFeature で必要なパーサーの振る舞いを決定する。
- パース時に、デフォルトの org.xml.sax.helpers.DefaultHandler は使わない。
の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を直接インプリすると、error、fatalError、warningを
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 スキーマ ページへ戻る。