Java iCalendar(ics)パース

## はじめに
事前に以下のライブラリを用意します。

- iCal4j
    - jarファイルをMaven Repositoryから取得すると使いやすいです。
    - https://mvnrepository.com/artifact/org.mnode.ical4j/ical4j/2.0.0
    - ※"Download ( JAR ) "のリンクからダウンロード
- Apache Commons Lang3
    - jarファイルをMaven Repositoryから取得すると使いやすいです。
    - https://mvnrepository.com/artifact/org.apache.commons/commons-lang3/3.5
    - ※"Download ( JAR ) "のリンクからダウンロード

iCalendar形式は、1行75octet以内とし、文字列が2行目以降に続く場合には2行目以降の行頭に空白が入ります。
iCal4jを普通に使うと2行目以降の空白でパースエラーになったため、icsファイルの内容を事前に整形しています(当方がiCal4jの実装を見落としている可能性があり、もっとよい方法があるかもしれません)。

パースするiCalendarは以下のファイルを使います。

```ical:input.ics
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//tool-taro//iCal 2.0//EN
BEGIN:VEVENT
UID:taro@tool-taro.com
DTSTART;VALUE=DATE:20160204
DTEND;VALUE=DATE:20160204
SUMMARY:三色団子1本目食べる
DESCRIPTION:あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に
 空白が入っています。
END:VEVENT
BEGIN:VEVENT
UID:taro@tool-taro.com
DTSTART;VALUE=DATE:20160205
DTEND;VALUE=DATE:20160205
SUMMARY:三色団子2本目食べる
DESCRIPTION:あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に
 空白が入っています。
END:VEVENT
BEGIN:VEVENT
UID:taro@tool-taro.com
DTSTART;VALUE=DATE:20160206
DTEND;VALUE=DATE:20160206
SUMMARY:三色団子3本目食べる
DESCRIPTION:あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に
 空白が入っています。
END:VEVENT
BEGIN:VEVENT
UID:taro@tool-taro.com
DTSTART;VALUE=DATE:20160207
DTEND;VALUE=DATE:20160207
SUMMARY:三色団子補充
DESCRIPTION:あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に
 空白が入っています。
END:VEVENT
END:VCALENDAR
```

## 実装例
サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。

```java:ICalParser.java
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.Property;

/**
 *
 * @author tool-taro.com
 */
public class ICalParser {

	public static void main(String[] args) throws FileNotFoundException, IOException, ParserException {

		//パースしたいicsファイルの保存場所
		String inputFilePath = "input.ics";
		//icsファイルのCharset
		Charset charset = StandardCharsets.UTF_8;

		//パース処理
		//iCal4jの2行目以降の実装が充分でない処理を補助
		StringBuilder icalBuilder = new StringBuilder();
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFilePath), charset));
			String line;
			while (true) {
				line = reader.readLine();
				if (line == null) {
					break;
				}
				if (!line.startsWith(" ")) {
					if (icalBuilder.length() > 0) {
						icalBuilder.append("\n");
					}
				}
				else {
					line = line.substring(1);
				}
				icalBuilder.append(line);
			}
			reader.close();

			CalendarBuilder builder = new CalendarBuilder();
			Calendar calendar = builder.build(new ByteArrayInputStream(icalBuilder.toString().getBytes(charset)));
			Component component;
			Property property;
			for (Iterator i = calendar.getComponents().iterator(); i.hasNext();) {
				component = (Component) i.next();
				System.out.format("Component=%1$s\n", component.getName());
				for (Iterator j = component.getProperties().iterator(); j.hasNext();) {
					property = (Property) j.next();
					System.out.format("Property=%1$s, %2$s\n", property.getName(), property.getValue());
				}
			}
		}
		finally {
			if (reader != null) {
				try {
					reader.close();
				}
				catch (IOException e) {
				}
			}
		}
	}
}
```

## 動作確認

```
$ javac ICalParser.java
$ java ICalParser
$ Component=VEVENT
Property=UID, taro@tool-taro.com
Property=DTSTART, 20160204
Property=DTEND, 20160204
Property=SUMMARY, 三色団子1本目食べる
Property=DESCRIPTION, あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に空白が入っています。
Component=VEVENT
Property=UID, taro@tool-taro.com
Property=DTSTART, 20160205
Property=DTEND, 20160205
Property=SUMMARY, 三色団子2本目食べる
Property=DESCRIPTION, あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に空白が入っています。
Component=VEVENT
Property=UID, taro@tool-taro.com
Property=DTSTART, 20160206
Property=DTEND, 20160206
Property=SUMMARY, 三色団子3本目食べる
Property=DESCRIPTION, あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に空白が入っています。
Component=VEVENT
Property=UID, taro@tool-taro.com
Property=DTSTART, 20160207
Property=DTEND, 20160207
Property=SUMMARY, 三色団子補充
Property=DESCRIPTION, あああ..こんな風に2行以上にわたる場合には2行目以降の行頭に空白が入っています。
```
## 環境
- 開発
	- Windows 10 Pro
	- JDK 1.8.0_121
	- NetBeans IDE 8.2

- 動作検証
	- CentOS Linux release 7.3
	- JDK 1.8.0_121

Webツールも公開しています。
[Web便利ツール@ツールタロウ](http://tool-taro.com/)