スキップしてメイン コンテンツに移動

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

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つのいずれかが良いでしょう。

コメント

このブログの人気の投稿

GolangでWindows GUIアプリケーション

GUIアプリ作成の前提 社内ツールとしてexeで配布 開発環境はGoLandを使う 社内ツールとしてexeを配布ということであれば、Visual StudioでC#による開発だと思います。しかしながら、Go言語を習得したいのと、GoLandの補完機能が便利で、Android Studio使っていたこともあり、とっつきやすいという点からGo縛りでGUIアプリケーションを考えたいと思います。 lxn/walk Windows application library kit for Go. Windows向けしか考えていないので、まずこのライブラリなのですが、ボタンやコンボボックスが思ったように並ばなかったり、手軽にイメージボタンを配置したりなど、簡単にレイアウトを変更できない課題に直面しました。レイアウト作成だけで時間を取られてしまいます。そう言えば、Androidアプリ開発のときは、XMLでデザイン部分を切り離してたのを思い出して、今回の調査の運びとなりました。 fyne-io/fyne Cross platform native GUIs designed for Go based on Material Design. Supports: Linux, macOS, Windows, BSD, iOS and Android. walkと比べると、クロスプラットフォームで作成できるのですが、こちらもコードの中にデザインを書いていく形でした。 therecipe/qt therecipe/qt allows you to write Qt applications entirely in Go or JavaScript. Qt Creatorなるものがあるようで、デザイン部分を切り離せる印象はあるものの、最新のPCでもビルドに時間がかかるようで、学習コストもかかるという記事を見かけて断念しました。 どうしたものかとツイートしましたら、下記のようにご助言をいただいた。 同じ課題に対し色々試した結果、PWAのフルキャッシュドに落ち着いた。中身はGo+WASMで。 https://t.co/e60whDTV16 — のぼのぼ📡 (@nobonobo) July 21, 2020 PWA 早速調べてみました。Googleが進めているプロジェクトで、ネイティブアプリのよ

Eclipseの高速化メモ

Eclipseが重いと一言に言っても、いろいろな工程での話があると思いますが、過去記事のなども含めてこの記事にピックアップしておきたいと思います。以下はWindows環境での話です。

Eclipseで一通り基本的なGitを使ってみる

備忘録的にまとめておきたいと思います。おかしな点があればご指摘いただければ幸いです。 目次 環境 プロジェクト作成~初コミットまで コミットの履歴&差分を見る 元に戻す ブランチについて ブランチを作成してみる ブランチを統合するには マージする Rebaseする ローカルリポジトリからプロジェクトを取り込む コミットしたメッセージの修正 環境 Eclipse IDE for Java Developers:Juno Service Release 1 Git:上記Eclipseのプリインストール版