RSSをSAXで読み込むプログラムを作ります。

SAXについて

org.xml.saxは、JDKに標準で入っている、 XMLをJavaプログラムから処理する為のフレームワークです。 SAX(Simple API for Xml)では、XML文書を読み込んでいる時に イベントが発生します。 発生したイベントはイベントハンドラで処理します。

SAXParserFactory spfactory = SAXParserFactory.newInstance();
SAXParser parser = spfactory.newSAXParser();
parser.parse(new File( XMLファイル名 ), ハンドラ );

SAXのイベントハンドラについて

SAXのイベントを処理するハンドラは、DefaultHandler?を継承して、 イベント発生時に動くメソッドをオーバーライドします。

  1. startDocument()
    1. イベント
      • ドキュメント開始時に呼ばれます
    2. 引数
      • なし
  2. endDocument()
    1. イベント
      • ドキュメント終了時に呼ばれます
    2. 引数
      • なし
  3. startElement()
    1. イベント
      • 開始タグが見つかったときに呼ばれます
    2. 引数
      • uri:String - URI
      • localName:String - 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
      • qName:String - 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列
      • attributes:Attributes - 指定された属性またはデフォルトの属性
  4. endElement()
    1. イベント
      • 終了タグが見つかったときに呼ばれます
    2. 引数
      • uri:String - URI
      • localName:String - 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列
      • qName:String - 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列
  5. characters()
    1. イベント
      • タグに挟まれた要素が見つかった時に呼ばれます
    2. 引数
      • ch:char[] - 文字
      • start:int - 文字配列内の開始位置
      • length:int - 文字配列から使用される文字数
  6. warning()
    1. イベント
      • パーサ警告の通知を受け取ります
    2. 引数
      • e:SAXParserException? - 例外としてエンコードされたエラー情報
  7. error()
    1. イベント
      • 回復可能なパーサエラーの通知を受け取ります
    2. 引数
      • e:SAXParserException? - 例外としてエンコードされたエラー情報
  8. fatalError()
    1. イベント
      • 致命的なパーサエラーの通知を受け取ります
    2. 引数
      • e:SAXParserException? - 例外としてエンコードされたエラー情報

タグ属性の解析

タグの開始イベントで返されるorg.xml.sax.Attributesを解析することによってタグ属性を解析することができます。

SAXプログラムの定跡

タグの開始イベント(startElement())で、タグ名をスタックにつんで、
タグの終了イベント(endElement())で、タグ名をスタックから引き出します。

 

そしてタグに挟まれた要素発見のイベント(character())では、スタックを参照して現在位置を調べて、対応する処理を行います。

ソースコード

public class RSSSAXReader extends DefaultHandler {
 // タグのスタック
 Stack tagStack = new Stack();

 public static void main(String[] args) {

   try {
     SAXParserFactory spf = SAXParserFactory.newInstance();
     spf.setNamespaceAware(true);
     spf.setValidating(true);

     org.xml.sax.XMLReader reader =
       spf.newSAXParser().getXMLReader();

     reader.setProperty(
       "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
       "http://www.w3.org/2001/XMLSchema");

     reader.setContentHandler(new RSSSAXReader());
     reader.setErrorHandler(new ParseErrorHandler());

     reader.parse(new InputSource(System.in));

   } catch (ParserConfigurationException e) {
     e.printStackTrace();
   } catch (SAXException e) {
     e.printStackTrace();
   } catch (IOException e) {
     e.printStackTrace();
   } catch (TransformerException e) {
     e.printStackTrace();
   }
 }

 public void startDocument() {
   System.out.println(" ==▼=====ドキュメント開始イベント");
 }

 public void endDocument() {
   System.out.println(" ==▲=====ドキュメント終了イベント");
 }

 public void startElement(
   String uri, String localName, String qName, Attributes attributes) {
   System.out.println(" " + tagStack + ":" + "▽タグ開始イベント");
   System.out.println(
     " " + tagStack + ":" + "--タグ属性=" + getAttrMap(attributes));

   tagStack.push(qName);
 }

 public void characters(char[] ch, int offset, int length) {
   // 注意:このままのコードでは取りこぼしあり!次章に解決策あり
   System.out.println(
     " " + tagStack + ":" + (new String(ch, offset, length)).trim());
 }

 public void endElement(String uri, String localName, String qName) {
   System.out.println(" " + tagStack + ":" + "△タグ終了イベント");

   tagStack.pop();
 }

 public Map getAttrMap(Attributes attributes) {
   Map attrMap = new HashMap();

   int length = attributes.getLength();

   for (int cnt = 0; cnt < length; cnt++) {
     attrMap.put(attributes.getQName(cnt), attributes.getValue(cnt));
   }

   return attrMap;
 }
}

読み込み結果

ハマったー

  1. 某社のXMLパーサーでかなり長いXMLをSAXで解析しているとき、約9000文字のところでタグの内容が切れてしまう現象が起きました。
  2. よくよく調べてみると、SAXの規格上は某社のXMLパーサーの実装が正しくて、それを使う私のアプリの実装がおかしいことが分かりました。
    org.xml.sax.ContentHandler#characters()のJavadocを見てみると、
    パーサは、このメソッドを呼び出して、各文字データチャンクを報告します。
    SAX パーサは、連続する文字データを単一のチャンクとして、
    またはいくつかのチャンクに分割して返します。
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    となっています。
  3. つまりは、
    <parent>
      <name>よたろう</name>
      <child>
        <name>じゅげむじゅげむごこうのすりきれかいじゃりすいぎょのすいぎょうまつ
    うんらいまつふうらいまつくうねるところにすむところやぶらこうじのぶらこうじ
    ぱいぽぱいぽぱいぽのしゅーりんがんしゅーりんがんのぐーりんだいぐーりんだいの
    ぽんぽこぴーのぽんぽこなーのちょうきゅうめいのちょうすけ</name>
      </child>
    </parent>
    を読み込むと以下のようにイベントが発生する可能性があるということ
    01 startDocument()
    02 startElement(parent)
    03 startElement(name)
    04 characters(よたろう)
    05 endElement(name)
    06 startElement(child)
    07 startElement(name)
    08 characters(じゅげむじゅげむ)
    09 characters(ごこうのすりきれ)
    …
    95 characters(ちょうすけ)
    96 endElement(name)
    97 endElement(child)
    98 endElement(parent)
    99 endDocument()
  4. ということで、chacarter()イベントでは引数の内容をため込むだけにして、タグ終了イベントでタグの内容を取得するようにしないとだめ。
    public class NewRSSSAXReader extends DefaultHandler {
     // タグのスタック
     Stack tagStack = new Stack();
     // Elementの格納場所
     StringBuffer leaf = null;
     
     public void startElement(
       leaf = new StringBuffer();
       tagStack.push(qName);
     }
    
     public void characters(char[] ch, int offset, int length) {
       if( leaf != null ){
         leaf.append( new String(ch, offset, length) );
       }
     }
    
     public void endElement(String uri, String localName, String qName) {
       if( leaf != null ){
         System.out.println( tagStack + ":" + leaf.toString().trim() );
         leaf = null;
       }
       tagStack.pop();
     }
    }
    • if(leaf!=null) となっているのは、タグとタグとの間の空白などでも character() イベントが起きる可能性があるため
      </child> (改行) </parent>
                ↑
               ココでcharacter()イベントが発生

Java


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS   sitemap
Last-modified: 2006-03-22 (水) 23:46:15 (3915d)
ISBN10
ISBN13
9784061426061