Commit e6cca076 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Unify VEVENT/VTODO parsing

parent 9459f80d
Pipeline #98164251 passed with stages
in 4 minutes and 25 seconds
......@@ -8,7 +8,7 @@
package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
import at.bitfire.ical4android.ICalendar.Companion.CALENDAR_NAME
import net.fortuna.ical4j.data.CalendarOutputter
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.*
......@@ -21,7 +21,6 @@ import java.io.IOException
import java.io.OutputStream
import java.io.Reader
import java.util.*
import java.util.logging.Level
class Event: ICalendar() {
......@@ -59,42 +58,22 @@ class Event: ICalendar() {
val unknownProperties = LinkedList<Property>()
companion object {
const val CALENDAR_NAME = "X-WR-CALNAME"
/**
* Parses an InputStream that contains iCalendar VEVENTs.
* Parses an iCalendar resource, applies [ICalPreprocessor] to increase compatibility
* and extracts the VEVENTs.
*
* @param reader where the iCalendar is taken from
* @param properties Known iCalendar properties (like [CALENDAR_NAME]) will be put into this map. Key: property name; value: property value
*
* @return array of filled [Event] data objects (may have size 0)
*
* @param reader reader for the input stream containing the VEVENTs (pay attention to the charset)
* @param properties map of properties, will be filled with CALENDAR_* values, if applicable (may be null)
* @return array of filled Event data objects (may have size 0) – doesn't return null
* @throws ParserException when the iCalendar can't be parsed
* @throws IllegalArgumentException when the iCalendar resource contains an invalid value
* @throws IOException on I/O errors
* @throws InvalidCalendarException on parsing exceptions
*/
fun fromReader(reader: Reader, properties: MutableMap<String, String>? = null): List<Event> {
Constants.log.fine("Parsing iCalendar stream")
// parse stream
val ical: Calendar
try {
ical = CalendarBuilder().build(reader)
} catch(e: ParserException) {
throw InvalidCalendarException("Couldn't parse iCalendar object", e)
} catch(e: IllegalArgumentException) {
throw InvalidCalendarException("iCalendar object contains invalid value", e)
}
try {
ICalPreprocessor.preProcess(ical)
} catch (e: Exception) {
Constants.log.log(Level.WARNING, "Couldn't pre-process iCalendar", e)
}
// fill calendar properties
properties?.let {
ical.getProperty(CALENDAR_NAME)?.let { calName ->
properties[CALENDAR_NAME] = calName.value
}
}
fun eventsFromReader(reader: Reader, properties: MutableMap<String, String>? = null): List<Event> {
val ical = fromReader(reader, properties)
// process VEVENTs
val vEvents = ical.getComponents<VEvent>(Component.VEVENT)
......
......@@ -10,12 +10,14 @@ package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.component.*
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.property.ProdId
import net.fortuna.ical4j.model.property.TzUrl
import java.io.Reader
import java.io.StringReader
import java.util.*
import java.util.logging.Level
......@@ -27,6 +29,7 @@ open class ICalendar {
var sequence: Int? = null
companion object {
// static ical4j initialization
init {
// reduce verbosity of those two loggers
......@@ -36,9 +39,59 @@ open class ICalendar {
Logger.getLogger(net.fortuna.ical4j.model.Recur::class.java.name).level = Level.CONFIG
}
// known iCalendar properties
const val CALENDAR_NAME = "X-WR-CALNAME"
/**
* Default PRODID used when generating iCalendars. If you want another value, set it
* statically before writing the first iCalendar.
*/
var prodId = ProdId("+//IDN bitfire.at//ical4android")
// parser
/**
* Parses an iCalendar resource and applies [ICalPreprocessor] to increase compatibility.
*
* @param reader where the iCalendar is taken from
* @param properties Known iCalendar properties (like [CALENDAR_NAME]) will be put into this map. Key: property name; value: property value
*
* @return parsed iCalendar resource
* @throws ParserException when the iCalendar can't be parsed
* @throws IllegalArgumentException when the iCalendar resource contains an invalid value
*/
fun fromReader(reader: Reader, properties: MutableMap<String, String>? = null): Calendar {
Constants.log.fine("Parsing iCalendar stream")
// parse stream
val calendar: Calendar
try {
calendar = CalendarBuilder().build(reader)
} catch(e: ParserException) {
throw InvalidCalendarException("Couldn't parse iCalendar", e)
} catch(e: IllegalArgumentException) {
throw InvalidCalendarException("iCalendar contains invalid value", e)
}
// apply ICalPreprocessor for increased compatibility
try {
ICalPreprocessor.preProcess(calendar)
} catch (e: Exception) {
Constants.log.log(Level.WARNING, "Couldn't pre-process iCalendar", e)
}
// fill calendar properties
properties?.let {
calendar.getProperty(CALENDAR_NAME)?.let { calName ->
properties[CALENDAR_NAME] = calName.value
}
}
return calendar
}
// time zone helpers
fun isDateTime(date: DateProperty?) = date != null && date.date is DateTime
......
......@@ -11,7 +11,6 @@
package at.bitfire.ical4android
import at.bitfire.ical4android.MiscUtils.TextListHelper.toList
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.data.CalendarOutputter
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.*
......@@ -64,23 +63,19 @@ class Task: ICalendar() {
companion object {
/**
* Parses an InputStream that contains iCalendar VTODOs.
* Parses an iCalendar resource, applies [ICalPreprocessor] to increase compatibility
* and extracts the VTODOs.
*
* @param reader reader for the input stream containing the VTODOs (pay attention to the charset)
* @return array of filled Task data objects (may have size 0) – doesn't return null
* @throws IOException
* @throws InvalidCalendarException on parser exceptions
* @param reader where the iCalendar is taken from
*
* @return array of filled [Task] data objects (may have size 0)
*
* @throws ParserException when the iCalendar can't be parsed
* @throws IllegalArgumentException when the iCalendar resource contains an invalid value
* @throws IOException on I/O errors
*/
fun fromReader(reader: Reader): List<Task> {
val ical: Calendar
try {
ical = CalendarBuilder().build(reader)
} catch(e: ParserException) {
throw InvalidCalendarException("Couldn't parse iCalendar object", e)
} catch(e: IllegalArgumentException) {
throw InvalidCalendarException("iCalendar object contains invalid value", e)
}
fun tasksFromReader(reader: Reader): List<Task> {
val ical = fromReader(reader)
val vToDos = ical.getComponents<VToDo>(Component.VTODO)
return vToDos.mapTo(LinkedList()) { this.fromVToDo(it) }
}
......
......@@ -21,9 +21,9 @@ class EventTest {
fun testCalendarProperties() {
javaClass.classLoader!!.getResourceAsStream("events/multiple.ics").use { stream ->
val properties = mutableMapOf<String, String>()
Event.fromReader(InputStreamReader(stream, Charsets.UTF_8), properties)
Event.eventsFromReader(InputStreamReader(stream, Charsets.UTF_8), properties)
assertEquals(1, properties.size)
assertEquals("Test-Kalender", properties[Event.CALENDAR_NAME])
assertEquals("Test-Kalender", properties[ICalendar.CALENDAR_NAME])
}
}
......@@ -203,7 +203,7 @@ class EventTest {
private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): List<Event> =
javaClass.classLoader!!.getResourceAsStream("events/$fname").use { stream ->
return Event.fromReader(InputStreamReader(stream, charset))
return Event.eventsFromReader(InputStreamReader(stream, charset))
}
}
......@@ -110,14 +110,14 @@ class TaskTest {
private fun parseCalendar(fname: String, charset: Charset = Charsets.UTF_8): Task {
javaClass.classLoader!!.getResourceAsStream("tasks/$fname").use { stream ->
return Task.fromReader(InputStreamReader(stream, charset)).first()
return Task.tasksFromReader(InputStreamReader(stream, charset)).first()
}
}
private fun regenerate(t: Task): Task {
val os = ByteArrayOutputStream()
t.write(os)
return Task.fromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8)).first()
return Task.tasksFromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8)).first()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment