2010年10月12日火曜日

android で SAX 使用時の characters() の動きについて

android で XML ファイルを読み込み際に、DOM より SAX が軽量とのことなので、
SAX について挙動を確認するためのサンプル・プログラムを作成してみました。

特に、これまで SAX なんて使ったことがなかった自分には、characters() の動きが、
想定外だったので、確認した内容を記録しておきます。


作成したサンプルですが、画面は以下のように3つの要素から構成されています。


上段の EditText で入力となる XML ファイルを指定し、
「ファイル読み込む」ボタンを押すと処理を開始し、
下段の EditText で出力ファイル名 (入力ファイル名 + .txt ) を表示しています。


以下はソースです。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<EditText
 android:id="@+id/edittext"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<Button
 android:id="@+id/button"
 android:text="ファイル読み込む"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
<TextView
 android:text="出力ファイル名:"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<EditText
 android:id="@+id/output"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:editable="false"
    />
</LinearLayout> 


TestSaxActivity.java
package jp.kochi;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class TestSaxActivity extends Activity {

 private EditText mEdit;
 private Button mButton;
 private EditText mOutput;
 private BufferedWriter mFileOutput;


    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mEdit   = (EditText) findViewById(R.id.edittext);
        mButton = (Button) findViewById(R.id.button);
        mOutput = (EditText) findViewById(R.id.output);

        mEdit.setText(getFilesDir() + "/");

        mButton.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    // ファイル名を取得
    CharSequence name = mEdit.getText();
    File file = new File(name.toString());

    // 出力用 のファイル名をセット
    String output = file.getName() + ".txt";
    mOutput.setText(output);
    try {
     // 出力ファイルオープン
     FileOutputStream out = openFileOutput(output, MODE_PRIVATE);
     mFileOutput = new BufferedWriter(new OutputStreamWriter(out));

     // SAXのよるparse開始
     parseSax(file);

    } catch (FileNotFoundException e) {
     e.printStackTrace();
    } finally {
     if (mFileOutput != null) {
      try {
       mFileOutput.close();
      } catch (IOException e) {
       e.printStackTrace();
      }
     }
    }
   }
  });

    }

    private void parseSax(File file) {

     SAXParserFactory factory = SAXParserFactory.newInstance();
  try {
   SAXParser parser = factory.newSAXParser();
   TestHandler handler = new TestHandler();
   parser.parse(file, handler);
  } catch (ParserConfigurationException e) {
   e.printStackTrace();
  } catch (SAXException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
    }

    private void writeToFile(String text) {

     try {
      mFileOutput.append(text);
  } catch (IOException e) {
   e.printStackTrace();
  }
    }

 private class TestHandler extends DefaultHandler {

  @Override
  public void startDocument() throws SAXException {
   super.startDocument();
   writeToFile("startDocument()\n");
  }

  @Override
  public void startElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException {
   super.startElement(uri, localName, qName, attributes);
   writeToFile("startElement() - uri            = " + uri + "\n");
   writeToFile("               - localName      = " + localName + "\n");
   for(int i=0; i<attributes.getLength(); i++) {
    writeToFile("               - attr.localName = " + attributes.getLocalName(i) + "\n");
    writeToFile("               - attr.Value     = " + attributes.getValue(i) + "\n");
   }
  }

  @Override
  public void characters(char[] ch, int start, int length)
    throws SAXException {
   super.characters(ch, start, length);

   String value = new String(ch, start, length);
   writeToFile("characters() - ch = \"" + value + "\"\n");
  }

  @Override
  public void endElement(String uri, String localName, String qName)
    throws SAXException {
   super.endElement(uri, localName, qName);
   writeToFile("endElement() - uri       = " + uri + "\n");
   writeToFile("             - localName = " + localName + "\n");
  }

  @Override
  public void endDocument() throws SAXException {
   super.endDocument();
   writeToFile("endDocument()\n");
  }

 }


}


実行例を以下のようになります。


test.xml (入力ファイル)
<?xml version="1.0" encoding="UTF-8" ?>
<test xmlns="http://www.hogehoge.com/hoge/2.0">
<dir id="d1">
  previous <text>Sample text1</text> after
</dir>
</test>

test.xml.txt (出力ファイル)
startDocument()
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = test
characters() - ch = "
"
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = dir
               - attr.localName = id
               - attr.Value     = d1
characters() - ch = "
"
characters() - ch = "  previous "
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = text
characters() - ch = "Sample text1"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = text
characters() - ch = " after"
characters() - ch = "
"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = dir
characters() - ch = "
"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = test
endDocument()

test.xml.txt の内容を見ると分かるのですが、
characters() では、改行や空白が、そのまま拾われるようになっています。

しかも面倒?なことに、1回で全てを取得するわけではなく、
改行の1バイトのみを characters() が返している箇所があります。

これまで SAX  を使ったことがある人には当然なのかもしれませんが、
使ったことがない自分には、ちょっと意外な動きでした。

この点は、要注意かと。