第 9 回 SAX

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

9-1. SAX

DOM は W3C が主導で規格化されました。 一方、SAX(Simple API for XML) は XML-DEV メーリングリストの有志によっ て開発されました。 これは、非常に単純な XML の API です。 パーサにハンドラ(オブザーバ)を与えておくことで、要素に遭遇したり、エラー が発生した場合にハンドラのメソッドを呼び出すというものです。

SAX は DOM のように XML を修正したり生成したりできず、読み込みのみです。 また、 DOM はドキュメントがメモリ上に配置され、プログラムがそれを探索 したりできますが、 SAX は XML 文書を読み込むときに順に様々なハンドラの メソッドが呼び出されるという、イベントドリブンなプログラミ ング手法になります。 但し、このような特徴から非常に軽量で高速でメモリも消費しません。 そのため、単なる読み込み、集計、オブジェクトの作成など、特定の用途には DOM より向いています。

SAXParser

javax.xml.parsers.SAXParser オブジェクトは、 parse メソッドで XML の構文解析を行います。 引数は入力と、 DefaultHandler の二つを取ります。 入力は、 java.io.File, org.xml.sax.InputSource, java.io.InputStream の他に、 URI を文字列で指定することもできます。

このオブジェクトを作るには、 javax.xml.parser.SAXParserFactory のファクトリメソッド newInstance() を呼び出し、さらに作られたオブジェクトに対して、newSAXParser メソッド を呼び出します。

DefaultHandler

org.xml.sax.helpers.DefaultHandler は org.xml.sax.ContentHandler の他、, DTDHandler, EntityResolver, ErrorHandler の各インターフェイスを実装したスケルトンクラスです。 なお、 org.xml.sax.ContentHandler は java.net.ContentHandler とインタ フェース名が一致するため、次のような import 文で次の表現を避ける必要が あります。


import java.net.*;
import org.xml.sax.*;
class X implements ContentHandler {
...

DefaultHandler はデフォルトコンストラクタを持ちます。 もちろんこれをそのまま parser に与えて動かすと、例外が発生する以外は何 もしません。 但し、この時も parser が構文解釈中に以下のメソッドを呼び出します。 そのため、構文解釈により何らかの出力を得たい場合は下記のメソッドをオー バライドします。

void startDocument()
文書の開始通知を受け取ります。
void endDocument()
文書の終了通知を受け取ります。
void startElement(String uri, String localName, String qName, Attributes attributes)
要素の開始通知を受け取ります。
void endElement(String uri, String localName, String qName)
要素の終了通知を受け取ります。
void characters(char[] ch, int start, int length)
要素内の文字データの通知を受け取ります。
void endPrefixMapping(String prefix)
名前空間マッピングの終了通知を受け取ります。
void error(SAXParseException e)
回復可能なパーサーエラーの通知を受け取ります。
void fatalError(SAXParseException e)
致命的な XML 構文解析エラーを報告します。
void ignorableWhitespace(char[] ch, int start, int length)
要素コンテンツに含まれる無視できる空白文字の通知を受け取ります。
void notationDecl(String name, String publicId, String systemId)
表記法宣言の通知を受け取ります。
void processingInstruction(String target, String data)
処理命令の通知を受け取ります。
InputSource resolveEntity(String publicId, String systemId)
外部エンティティーを解決します。
void setDocumentLocator(Locator locator)
文書イベントの Locator オブジェクトを受け取ります。
void skippedEntity(String name)
スキップされたエンティティーの通知を受け取ります。
void startPrefixMapping(String prefix, String uri)
名前空間マッピングの開始通知を受け取ります。
void unparsedEntityDecl(String name, String publicId, String systemId, String notationName)
解析対象外エンティティー宣言の通知を受け取ります。
void warning(SAXParseException e)
パーサー警告の通知を受け取ります。

なお、startElement の引数の Attributes は DOM のオブジェクトではなく、 org.xml.sax.Attributes 型です。 下記にメソッドを示します。

Attributes

int getIndex(String qName)
XML 修飾名 (接頭辞付き) を指定して属性のインデックスを検索します。
int getIndex(String uri, String localName)
名前空間名を指定して属性のインデックスを検索します。
int getLength()
リスト内にある属性の数を返します。
String getLocalName(int index)
インデックスを指定して属性のローカル名を検索します。
String getQName(int index)
インデックスを指定して属性の XML 修飾名 (接頭辞付き) を検索します。
String getType(int index)
インデックスを指定して属性の型を検索します。
String getType(String qName)
XML 修飾名 (接頭辞付き) を指定して属性の型を検索します。
String getType(String uri, String localName)
名前空間名を指定して属性の型を検索します。
String getURI(int index)
インデックスを指定して属性の名前空間 URI を検索します。
String getValue(int index)
インデックスを指定して属性の値を検索します。
String getValue(String qName)
XML 修飾名 (接頭辞付き) を指定して属性の値を検索します。
String getValue(String uri, String localName)
名前空間名を指定して属性の値を検索します。

9-2. SAX の用例

単なる表示

例9-1

単純に要素の開始時に、与えられる情報を表示するだけのプログラムを示しま す。


import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
class TestHandler extends DefaultHandler {
    public TestHandler(){}
    @Override
    public void startElement(String uri, String localname,
			     String qname, Attributes attributes){
	System.out.println("要素");
	System.out.println("URI:"+uri);
	System.out.println("LocalName:"+localname);
	System.out.println("QName"+qname);

	for(int i=0; i<attributes.getLength(); i++){
	    System.out.println(" 属性"+i);
	    System.out.println(" LocalName:"+attributes.getLocalName(i));
	    System.out.println(" QName:"+attributes.getQName(i));
	    System.out.println(" Type:"+attributes.getType(i));
	    System.out.println(" Value:"+attributes.getValue(i));
	}
    }
}
class Rei {
    private static SAXParser getSAXParser() throws Exception {
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	parserFactory.setNamespaceAware(true);
	return parserFactory.newSAXParser();
    }
    public static void main(String[] arg) throws Exception {
	SAXParser parser = getSAXParser();
	parser.parse(System.in,new TestHandler());
    }
}

集計

例9-2

次に次のような XML 文書を考えます。

<?xml version="1.0" encoding="Shift_JIS" standalone="no"?>
<itemlist>
  <item>
    <data name="品名" value="りんご"/>
    <data name="単価" value="200"/>
    <data name="個数" value="3"/>
    <data name="合計" value="600"/>
  </item>
  <item>
    <data name="品名" value="みかん"/>
    <data name="単価" value="100"/>
    <data name="個数" value="5"/>
    <data name="合計" value="500"/>
  </item>
  <item>
    <data name="品名" value="もも"/>
    <data name="単価" value="300"/>
    <data name="個数" value="1"/>
    <data name="合計" value="300"/>
  </item>
</itemlist>

これに対して、<data name="合計" value="xxx"/> という要素のみを取 り出し、合計の値を合算して合計値を計算するプログラムを示します。


import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
class ExHandler extends DefaultHandler {
    private int total;
    public ExHandler(){
	total=0;
    }
    public int getTotal(){
	return total;
    }
    @Override
    public void startElement(String uri, String localname,
			     String qname, Attributes attributes){
	if(!localname.equals("data")){
	    return;
	}
	if(!attributes.getValue("name").equals("合計")){
	    return;
	}
	total += Integer.parseInt(attributes.getValue("value"));
    }
}
class Rei {
    private static SAXParser getSAXParser() throws Exception {
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	parserFactory.setNamespaceAware(true);
	return parserFactory.newSAXParser();
    }
    public static void main(String[] arg) throws Exception {
	SAXParser parser = getSAXParser();
	ExHandler handler = new ExHandler();
	parser.parse(System.in,handler);
	System.out.println(handler.getTotal());
    }
}

オブジェクトの作成

例9-3

次に、 XML の入力にしたがって、オブジェクトを作成します。

まず、作成するオブジェクトとして、コンストラクタ、各値の setter、 toString を与えたクラスを示します。


class Item {
    public Item(){
    }
    private  String name;
    private int price;
    private int number;
    private int total;
    public void setName(String name){
	this.name = name;
    }
    public void setPrice(int price){
	this.price = price;
    }
    public void setNumber(int number){
	this.number = number;
    }
    public void setTotal(int total){
	this.total = total;
    }
    @Override public String toString(){
	return name+": 単価 "+price+", 個数 "+number+", 合計 "+total;
    }
}

次に、 main を先に示します。 これは java.util.LinkedList のオブジェクトを作り、 ExHandler のコンス トラクタに与えています。 そして、この ExHandler が XML を読みながらオブジェクトのリストを作成し ます。 読み終えたら各要素を表示します。


import javax.xml.parsers.*;
import java.util.*;
import org.xml.sax.*;
class Rei {
    private static SAXParser getSAXParser() throws Exception {
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	parserFactory.setNamespaceAware(true);
	return parserFactory.newSAXParser();
    }
    public static void main(String[] arg) throws Exception {
	SAXParser parser = getSAXParser();
	LinkedList<Item> list = new LinkedList<Item>();
	ExHandler handler = new ExHandler(list);
	parser.parse(System.in,handler);
	for(Item i: list){
	    System.out.println(i);
	}
    }
}

ExHandler では startElement で次の処理をします。

  1. item を読み始めたら Item オブジェクトを作り、リストに加えます。
  2. data を読み始めたら、各 attribute の name ごとに setter を 使用して値をセットします。

import java.util.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
class ExHandler extends DefaultHandler {
    private List<Item> list;
    public ExHandler(List<Item> list){
	this.list = list;
    }
    public List<Item> getList(){
	return list;
    }
    private Item item=null;
    @Override
    public void startElement(String uri, String localname,
			     String qname, Attributes attributes){
	if(localname.equals("item")){
	    item = new Item();
	    return;
	}
	if(!localname.equals("data")){
	    return;
	}
	if(attributes.getValue("name").equals("品名")){
	    item.setName(attributes.getValue("value"));
	    return;
	}
	if(attributes.getValue("name").equals("単価")){
	    item.setPrice(Integer.parseInt(attributes.getValue("value")));
	    return;
	}
	if(attributes.getValue("name").equals("個数")){
	    item.setNumber(Integer.parseInt(attributes.getValue("value")));
	    return;
	}
	if(attributes.getValue("name").equals("合計")){
	    item.setTotal(Integer.parseInt(attributes.getValue("value")));
	    return;
	}
    }
    @Override
    public void endElement(String uri, String localname,
			     String qname){
	if(!localname.equals("item")){
            return;
        }
        list.add(item);
    }
}

9-3. SAX による妥当性のチェック

SAX パーサは整形式のチェックだけではなく、 DTD や XML Schema に対する 妥当性もチェックできます。

DTD

DTD のチェックをするには javax.xml.parser.SAXParserFactory オブジェク トに対して setValidating メソッドで true を指定すると構文チェックしま す。 この時、妥当性に問題がある場合は、与える DefaultHandler の error メソッ ドが呼び出されます。 そのため、実際に DefaultHandler を継承したクラスでは error メソッドを継承し、 与えられた例外に対して、適切な処理を行います。 以下が構文チェックをする最小限のプログラムです。


import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.validation.*;
class TestHandler extends DefaultHandler {
    public TestHandler(){}
    @Override
    public  void error(SAXParseException e) throws SAXException {
	System.out.println(e);
    }
}
class Rei {
    private static SAXParser getSAXParserWDTD() throws Exception {
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	parserFactory.setValidating(true);
	parserFactory.setNamespaceAware(true);
	return parserFactory.newSAXParser();
    }
    public static void main(String[] arg) throws Exception {
	SAXParser parser = getSAXParserWDTD();
	parser.parse(System.in,new TestHandler());
    }
}

これに対して下記のような XML 文書が妥当性の検査に成功します。

例9-4

<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<!DOCTYPE class [ 
<!ELEMENT class (teacher+,time+)>
<!ATTLIST class name CDATA #REQUIRED>
<!ELEMENT teacher EMPTY>
<!ATTLIST teacher name CDATA #REQUIRED>
<!ELEMENT time EMPTY>
<!ATTLIST time day (mon|tue|wed|thu|fri|sat|sun) #REQUIRED
  period CDATA #REQUIRED>
]>
<class name="データ構造とアルゴリズム II" >
<teacher name="坂本直志" />
<time day="tue" period="2" />
</class>

例9-5

class.dtd
<!ELEMENT class (teacher+,time+)>
<!ATTLIST class name CDATA #REQUIRED>
<!ELEMENT teacher EMPTY>
<!ATTLIST teacher name CDATA #REQUIRED>
<!ELEMENT time EMPTY>
<!ATTLIST time day (mon|tue|wed|thu|fri|sat|sun) #REQUIRED
  period CDATA #REQUIRED>
XML文書
<?xml version="1.0" encoding="shift_jis" standalone="no" ?>
<!DOCTYPE class SYSTEM "class.dtd" >
<class name="データ構造とアルゴリズム II" >
<teacher name="坂本直志" />
<time day="tue" period="2" />
</class>

XML Schema

XML Schema のチェックをするには javax.xml.parser.SAXParserFactory オブジェク トに対して setValidating メソッドに false を指定する一方、 setSchema で XMLSchema のパーサを与えます。 以下が構文チェックをする最小限のプログラムです。


import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.validation.*;
class TestHandler extends DefaultHandler {
    public TestHandler(){}
    @Override
    public  void error(SAXParseException e) throws SAXException {
	System.out.println(e);
    }
}
class Rei {
    private static SAXParser getSAXParserWSchema() throws Exception {
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	parserFactory.setValidating(false);
	parserFactory.setNamespaceAware(true);
	SchemaFactory sf = SchemaFactory.newInstance(
                                    XMLConstants.W3C_XML_SCHEMA_NS_URI);
	parserFactory.setSchema(sf.newSchema());
        return parserFactory.newSAXParser();
    }
    public static void main(String[] arg) throws Exception {
	SAXParser parser = getSAXParserWSchema();
	parser.parse(System.in,new TestHandler());
    }
}

これに対して以下の XML 文書は妥当性チェックに成功します。

例9-6

class.xsd
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="class" type="classType" />
  <xsd:complexType name="classType">
    <xsd:sequence>
      <xsd:element name="teacher" type="teacherType" />
      <xsd:element name="time" type="timeType" />
    </xsd:sequence>
    <xsd:attribute name="name" type="xsd:string"/>
  </xsd:complexType>
  <xsd:complexType name="teacherType">
    <xsd:attribute name="name" type="xsd:string"/>
  </xsd:complexType>
  <xsd:complexType name="timeType">
    <xsd:attribute name="day" type="dayType"/>
    <xsd:attribute name="period" type="xsd:int"/>
  </xsd:complexType>
  <xsd:simpleType name="dayType">
    <xsd:restriction base="xsd:string">
      <xsd:enumeration value="mon"/>
      <xsd:enumeration value="tue"/>
      <xsd:enumeration value="wed"/>
      <xsd:enumeration value="thu"/>
      <xsd:enumeration value="fri"/>
      <xsd:enumeration value="sat"/>
      <xsd:enumeration value="sun"/>
    </xsd:restriction>
  </xsd:simpleType>
</xsd:schema>
XML文書
<?xml version="1.0" encoding="shift_jis" standalone="no" ?>
<class name="データ構造とアルゴリズム II"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="class.xsd"
>
<teacher name="坂本直志" />
<time day="tue" period="2" />
</class>

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科