概要
JavaでRSSフィードを取得してパースしているときに発生しました
原因はXML内にunicodeが含まれるためでした
力技ですがunicodeが含まれている場合の対応コードを書いたので紹介します
本当はRSSを配信しているサービスとかASP側で対応してほしいものですが。。。
環境
- Java 1.7
- Eclipse 4.4(Luna)
- rome-fetcher 0.9
対応方法
今回、自分はrome-fetherというJavaのRSSパーサを使っていました
エラーが発生する箇所はFeedFetcher.retrieveFeed
というメソッドをコールした部分で
retrieveFeedの引数に指定したRSSフィードのXMLがぶっ壊れているとParsingFeedException
が発生するようです
対応方法はだいぶ力技な感じがしますがParsingFeedException
or SAXException
が発生したら独自のRSSパーサがコールされ、独自のパーサでは取得したRSS情報のXMLからunicode文字を削除した上でXMLの解析を開始させます
やっていることは以上でソース的には以下のような感じにしました
package test;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* 独自のRSSパーサをテストするためのクラス
*
* @author kakakikikeke
*
*/
public class RssReader {
String uri;
public RssReader(String uri) {
super();
this.uri = uri;
}
/**
* 独自のRSSパーサ
*/
public void fetcher() {
// Javaがデフォルトで提供するDOM解析クラスを使ってパースします
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
factory.setValidating(true);
Node root = null;
try {
// 何も考えずパースする、XMLが壊れている場合はSAXExceptionが発生する
root = builder.parse(uri);
} catch (SAXException e) {
// XMLが壊れている場合はunicodeを取り除いた上でパースする
root = builder.parse(getTrimmedUnicodeXML(), "UTF-8");
}
// パースが完了したらDOM構造から必要な情報を取得する
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
if (nl.item(i).getNodeName().equals("rss")) {
NodeList nl2 = nl.item(i).getChildNodes();
for (int j = 0; j < nl2.getLength(); j++) {
NodeList nl3 = nl2.item(j).getChildNodes();
for (int k = 0; k < nl3.getLength(); k++) {
if (nl3.item(k).getNodeName().equals("item")) {
NodeList nl4 = nl3.item(k).getChildNodes();
for (int l = 0; l < nl4.getLength(); l++) {
System.out.println(nl4.item(l).getNodeName());
System.out.println(nl4.item(l).getFirstChild().getNodeValue());
}
System.out.println();
}
}
}
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* unicode文字を取り除したXML情報を取得する
*
* @return
* @throws IOException
*/
public InputStream getTrimmedUnicodeXML() throws IOException {
// RSSフィードを指定してXMLを取得し文字列に格納する
URL url = new URL(uri);
HttpURLConnection urlconn = (HttpURLConnection)url.openConnection();
urlconn.setRequestProperty("Accept-Charset", "UTF-8");
urlconn.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(urlconn.getInputStream(), "UTF-8"));
String xml = "";
while (true){
String line = reader.readLine();
if ( line == null ){
break;
}
xml += line;
}
reader.close();
urlconn.disconnect();
// 取得したXMLからunicode文字を取り除く
xml = xml.replaceAll("[\\00-\\x08\\x0a-\\x1f\\x7f]", "");
// 最終的にInputStreamで渡す必要があるため一旦ファイルに書き出す
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("rss20.xml"),"UTF-8");
PrintWriter pw = new PrintWriter(osw);
pw.write(xml);
pw.close();
osw.close();
// ファイルからInputStremを生成する
InputStream in = new FileInputStream("rss20.xml");
return in;
}
/**
* 実行メインクラス
*
* @param args
*/
public static void main(String[] args) {
// ここにパースが失敗するRSSフィードのURLを指定してください
String uri = "http://rssblog.ameba.jp/figma/rss20.xml";
RssReader rss2 = new RssReader(uri);
rss2.fetcher();
}
}
ポイントは
root = builder.parse(uri);
の部分でここでSAXExceptionが発生したらgetTrimmedUnicodeXML
でunicode文字が除外されたXMLを元にparseを実行します
parse
メソッドはjavax.xml.parsers.DocumentBuilder
クラスのメソッドでInputStream
を引数にすることでパースすることもできます
なのでgetTrimmedUnicodeXMLでは一旦XMLを取得した上でStringに格納しreplaceAllメソッドでunicode文字を空白に置換した上で
さらにファイルに書き込んでそれを読み込むことでInputStreamを生成しています
(この辺はわざわざファイルにしなくてもInputStreamを生成できるもっといい方法があるかもしれません)
流れはだいたいそんな感じです
一応これを使ったらちゃんとパースして目的の情報を取得することはできました
それでもまだエラーになってしまう場合はreplaceAllする文字の種類を増やしてみてください
XMLの詳しいルールはわかりませんが、まだXML内に含んではいけない文字が含まれているはずです