XMLファイルをコピーする(2)


最初はJava + SAX (Simple API for XML)でやってみます.プログラムは以下のようなものです.(エラー処理は全然やってありません.)

import org.xml.sax.*;
import org.xml.sax.helpers.*;

import java.io.*;

public class Main {
    
    public static void main(String args) {
        PrintStream ps = createPrintStream("MusicLibrary_Copy.xml");
        XMLReader xr = createXMLReader(ps);
        try{
            xr.parse(new InputSource(new FileReader("MusicLibrary.xml")));
        }
        catch (Exception e){
            e.printStackTrace();
        }
        ps.close();
    }

    public static XMLReader createXMLReader(PrintStream ps){
        try{
            XMLReader xr=XMLReaderFactory.createXMLReader();
            xr.setContentHandler(new Main().new DefaultHandlerImpl(ps));
            return xr;
        } 
        catch (Exception e){
            return null;
        }
    }
    
    public static PrintStream createPrintStream(String fileName){
        OutputStream os;
        try{
            os =new FileOutputStream(fileName);
            PrintStream ps;
            try{
                ps =  new PrintStream(os,true,"UTF-8");
                ps.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                return ps;
            } catch (Exception e){
                e.printStackTrace();
            }
            
        } catch (FileNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }

    public class DefaultHandlerImpl extends DefaultHandler{
        public DefaultHandlerImpl(PrintStream ps){
            this.ps=ps;
        }
        private PrintStream ps;

        public void startElement(String namespaceURI,
                                 String localName,
                                 String qName,
                                 Attributes attr) throws SAXException{
            ps.print("<" + localName);
            for (int i=0; i < attr.getLength(); i++){
                ps.print(" "+attr.getLocalName(i)+"=\""+attr.getValue(i)+"\"");
            }
            ps.print(">");
        }

        public void endElement(String namespaceURI,
                               String localName,
                               String qName) throws SAXException{
            ps.print("</" + localName + ">");
        }
        public void characters(char ch,
                                int start,
                                int length) throws SAXException{
            for (int i=0;i<length;i++){
                char outChar=ch[start + i];
                ps.print(outChar);
            }
        }
    }
}

このプログラムではXMLReaderのインスタンスxrを生成してコンテンツを処理するDefaultHandlerImplクラスのインスタンスを登録し、xr.parseで一気に読み込みます.あとは、コンテンツが処理されるたびにDefaultHandlerImplクラスのメソッドがコールバックで起動されるという筋書きです.

簡単にするために、各メソッドでは
1.要素の始まりでは"<"+要素名+属性値+">"をPrintStreamに書き出します.
2.要素間のテキストはそのまま内容をPrintStreamに書き出します.
3.要素の終了では"</"+要素名+">"をPrintStreamに書き出します.
という処理を直にやっています.

構造からご覧になってわかると思いますが、

1.SAXはいったん走り出したら途中で止めるということができません.(例外でも投げれば止められるでしょうか、キレイではありません)
2.イベント処理です.つまりイベントを受け取った時点で処理できなければ、自分で記憶させる必要があります.SAX側はイベントを発生させてくれますが、そのあとの面倒まで見てくれません.例えばpart/title,chapter/title、section/titleで処理を切り分けたければ、親がどの要素であるかを記憶しておかねばなりません.

という感じです.もしXMLのフィルターのようなプログラムを作るのだったら、ほぼ出力も入力と同じ構造をしており、またあまり深い階層にならないものならSAXでも作れるでしょう.もしそうでなければ、SAXで読んで自分でメモリー上にJavaのクラス(のインスタンス)に展開してやらないと無理です.

SAXはDITA Open Toolkitでも使われています.DITAのテーブルはCALSのテーブルですが、入力にentry/@colnameがなくても、このSAXでの読み込みでちゃんとつけてくれます.DocBookはそのような仕組みがないので、スタイルシートで自前で@colnameを判定する(つうまり自分が何カラム目か判定する)処理をやっています.

SAXはPUSH型のパーサーと言われます.パーサーからハンドラーの側にどんどんイベント(コンテンツ)がPUSHされるからです.次はPULL型の処理を見てみたいと思います.