文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

Javaのstaxでxmlをパースする

stax使ってみますよ〜


f:id:treeapps:20180426142529p:plain

staxでxmlをパースするサンプルです。
XMLStreamReaderとXMLEventReaderがありますが、XMLEventReaderは微妙に使いにくいのでXMLStreamReaderを使います。

livedoor天気情報のapiを使って、locationの属性名・値、descriptionの要素名・値を取得します。
apiは以下のようなxmlを返します。

<lwws version="livedoor Weather Web Service 1.0">
<author>livedoor Weather Team.</author>
<location area="関東" pref="神奈川県" city="横浜"/>
<title>神奈川県 横浜 - 明日の天気</title>
<link>http://weather.livedoor.com/area/14/70.html?v=1</link>
<forecastday>tomorrow</forecastday>
<day>Thursday</day>
<forecastdate>Thu, 14 Jun 2012 00:00:00 +0900</forecastdate>
<publictime>Tue, 12 Jun 2012 17:00:00 +0900</publictime>
<telop>曇時々晴</telop>
<description>
神奈川県では、12日夜のはじめ頃から13日昼前まで強風に注意して下さい。 本州の南海上には前線が停滞しています。前線上の四国沖には低気圧があって東に進んで...<br />[PR]<a href="http://weather.livedoor.com/indexes/cloth_wash/?r=rest_pr">きょうは洗濯できるかな?</a>
</description>
・・・以下略・・・

ではこのxmlレスポンスをパースするコードを書いてみます。

public class XMLStreamReaderTest {

    public static void main(String[] args) throws MalformedURLException, IOException, XMLStreamException {
        URLConnection con = new URL("http://weather.livedoor.com/forecast/webservice/rest/v1?city=70&day=tomorrow").openConnection();
        Reader r = new InputStreamReader(con.getInputStream(), "UTF-8");
        new XMLStreamReaderTest().run(r);
    }

    public void run(Reader r) throws FileNotFoundException, XMLStreamException {
        final StreamFilter filter = new StreamFilter() {
            @Override
            public boolean accept(XMLStreamReader reader) {
                return reader.isStartElement() || reader.isEndElement();
            }
        };
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createFilteredReader(factory.createXMLStreamReader(r), filter);
        while (reader.hasNext()) {
            reader.next();
            if (reader.isStartElement()) {
                System.out.println("START:" + reader.getLocalName());
                if ("location".equals(reader.getLocalName())) {
                    for (int i = 0; i < reader.getAttributeCount(); i++) {
                        System.out.println(reader.getAttributeLocalName(i) + "=" + reader.getAttributeValue(i));
                    }
                } else if ("description".equals(reader.getLocalName())) {
                    System.out.println(reader.getElementText());
                }
            } else if (reader.isEndElement()) {
                System.out.println("END:" + reader.getLocalName());
            }
        }
        reader.close();
    }
}

実行すると以下のようにコンソールに表示されます。

START:author
END:author
START:location
area=関東
pref=神奈川県
city=横浜
END:location
・・・略・・・
START:description
神奈川県では、12日夜のはじめ頃から13日昼前まで強風に注意して下さい。

本州の南海上には前線が停滞しています。前線上の四国沖には低気圧があって東に進んで...<br />[PR]<a href="http://weather.livedoor.com/indexes/cloth_wash/?r=rest_pr">きょうは洗濯できるかな?</a>
・・・略・・・

実際パースする場合は結果をdtoのlistに詰めて永続化する事が多いとおもいます。
例えば以下のようなxmlが返る場合、

<pinpoint>
	<location>
		<title>横浜市</title>
	</location>
	<location>
		<title>横浜市鶴見区</title>
	</location>
</pinpoint>

isStartElement()でdtoをnewし、isEndElement()でlistにdtoをaddすれば簡単にlistに詰められるかと思います。

List<HogeDto> list = new ArrayList<HogeDto>();
HogeDto dto = null;
while (reader.hasNext()) {
	reader.next();
	if (reader.isStartElement()) {
		if ("location".equals(reader.getLocalName())) {
			dto = new HogeDto();
		} else if ("title".equals(reader.getLocalName())) {
			dto.title = reader.getElementText();
		}
	} else if (reader.isEndElement()) {
		if ("location".equals(reader.getLocalName())) {
			list.add(dto);
		}
	}
}

コードを見ただけではxmlの構造が想像できませんが、
domより綺麗で良い感じのコードになりますね。