Android、Java、Web系、Linux、マラソン等の備忘録

2012/10/26

AndroidでのXML解析と、Evernote本文のDocumentパースが激重い話

0 件のコメント

org.w3c.dom.Documentを使う場合

親ノードを辿れたり、タグの属性を指定して更新できたりしてコードがすっきりするのでこのAPIを使いたいわけですが、EvernoteのクラウドAPIで取得したnoteの本文を丸々突っ込むとパースがクソ重いです。コードは以下のようになるかと思います。

String content = "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\"><en-note><div><p><en-todo checked=\"true\"></en-todo>チェック項目1</p></div><div>ほげほげ</div></en-note>";

InputStream in = new ByteArrayInputStream(content.getBytes());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(in);

パースが遅い故、デバッグもし辛いしorg.w3c.domの利用を諦めようとしていましたが、<en-note>~</en-note>を抜き取ったものを使うと劇的に速くなります。理由までは調べてませんが...
String contentとInput Streamの宣言の間に以下のように、文字列処理を行うと良いでしょう。

int index = content.indexOf("<en-note");
content = content.substring(index);

これで取得したdocumentを
NodeList node_list = document.getElementsByTagName("a");

のように指定したタグでリストを作ってくれるので、僕にとってはXMLをイメージしやすく扱いやすいです。

※ソースは現在実装中のものから抜き出し見やすくしているので、コンパイルは通してません。考え方の参考までに。

XmlPullParserを使う場合

ググるとAndroidではXmlPullParserを使う例が多いようです。
        try {
            xmlPullParser.setInput( new StringReader ( content ) );
        } catch (XmlPullParserException e) {
             Log.v(TAG, "Error");
        }

        try {
            int eventType;
            while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
                Log.v(TAG, "name:" + xmlPullParser.getName() + " text:" + xmlPullParser.getText());
            }
        } catch (Exception e) {
            Log.v(TAG, "Error:" + e.getMessage());
        }

ノートの本文は上記同様String型のcontentに。データを丸々突っ込んでも快適に動作します。直感的ですが、while文を回して取得して文字列を辿るのが今回は適さないので深くは見てません。

SAXParserとDefaultHandlerを使う場合

Java関連でググると軽量なXMLの解析はSAXが提案されてました。parseを実行すると以下のNoteSAXHandler内のメソッドが実行されます。

private final static String TAG = "testActionSampleSAX";

public void testActionSampleSAX() throws Exception {

    String content = (note本文);

    // SAXパーサーファクトリを生成
    SAXParserFactory factory = SAXParserFactory.newInstance();
    // SAXパーサーを生成
    SAXParser parser = factory.newSAXParser();
    //XMLパーサーを定義 別途DefaultHandler を継承したクラス生成必要
    NoteSAXHandler handler = new NoteSAXHandler();


    InputStream is = new ByteArrayInputStream(content.getBytes());

    //パーサー実行
    parser.parse(is, handler);
}


private class NoteSAXHandler extends DefaultHandler {
    // ContentHandlerの実装
    public void startDocument() throws SAXException {
        Log.v(TAG, "startDocument()");
    }
    public void endDocument() throws SAXException {
        Log.v(TAG, "endDocument()");
    }
    public void startElement(java.lang.String uri,
                       java.lang.String localName,
                       java.lang.String qName,
                       Attributes atts)
                throws SAXException {
        Log.v(TAG, "startElement()");
        Log.v(TAG, "namespace=" + uri);
        Log.v(TAG, "local name=" + localName);
        Log.v(TAG, "qualified name=" + qName);
        for (int i = 0; i < atts.getLength(); i++) {
            Log.v(TAG, "attribute name=" + atts.getLocalName(i));
            Log.v(TAG, "attribute qualified name=" + atts.getQName(i));
            Log.v(TAG, "attribute value=" + atts.getValue(i));
        }
    }
    public void endElement(java.lang.String uri,
                       java.lang.String localName,
                       java.lang.String qName)
                throws SAXException {
        Log.v(TAG, "endElement()");
    }
    public void characters(char[] ch,
                       int start,
                       int length)
                throws SAXException {
        Log.v(TAG, "characters()" + new String(ch, start, length));
    }

    // ErrorHandlerの実装
    public void warning(SAXParseException e) {
        Log.v(TAG, "警告: " + e.getLineNumber() +"行目:" + e.getMessage());
    }
    public void error(SAXParseException e) {
        Log.v(TAG, "エラー: " + e.getLineNumber() +"行目:" + e.getMessage());
    }
    public void fatalError(SAXParseException e) {
        Log.v(TAG, "深刻なエラー: " + e.getLineNumber() +"行目:" + e.getMessage());
    }
}


タグ名などはstartElementメソッドで取得でき、タグに囲まれたテキストはcharactersメソッドで取得します。このように、タグを探して中身を取得&更新みたいな事をしようとすると、若干トリッキーな実装になりそうだったのでちょっと扱いづらいです。しかし、本文を丸々突っ込んでも快適に動作します。

最後に

動作を試したのは、いずれも少ないデータでの話なので、決定的な速度の違いはありませんでしたが、データが大きくなれば特徴が現れるかもしれません。でも、一番上のやり方でちょっと触れたように、パースが遅ければ文字列処理で削るなどの方法でなんとかなるんじゃないかと思いますし、あまりトリッキーな事はバグの原因やテストが増えるので、一番上のやり方を使いたいですね。まあ単にデータを引っ張ってくるなら、下2つのいずれかが良いでしょう。

0 件のコメント :

コメントを投稿