arnaudq's blog
iCalendar to XML conversion using ical4j
The ical4j library is probably the reference java library for manipulating iCalendar data. It offers a full representation of the iCalendar (RFC 2445) data model (calendar components, properties and parameters), a parser, as well a set of helper classes to do date/time calculations.
A nice design point is that there is a good separation between the act of parsing an iCalendar stream and the building of ical4j objects.
This is achieved by using an event driven model where the parser calls back a handler object whenever a new event (beginning/end of a component or property,...) is encountered by the parser. In other words, the parser is the iCalendar equivalent of an XML SAX parser.
Here is the interface that a handler has to implement:
public interface ContentHandler {
/**
* Triggers the start of handling a calendar.
*/
void startCalendar();
/**
* Triggers the end of handling a calendar.
*/
void endCalendar();
/**
* Triggers the start of handling a component.
*/
void startComponent(String name);
/**
* Triggers the end of handling a component.
*/
void endComponent(String name);
/**
* Triggers the start of handling a property.
*/
void startProperty(String name);
/**
* Triggers the handling of a property value.
*/
void propertyValue(String value) throws URISyntaxException, ParseException,
IOException;
/**
* Triggers the end of handling a property.
*/
void endProperty(String name);
/**
* Triggers the handling of a parameter.
*/
void parameter(String name, String value) throws URISyntaxException;
}
And here is a sample class implementing this interface to produce a very basic iCalendar to XML conversion (using the StAX XML API):
public final class XMLHandler implements ContentHandler {
private final XMLStreamWriter xmlWriter;
public XMLHandler(XMLStreamWriter xmlWriter) {
this.xmlWriter = xmlWriter;
}
/**
* {@inheritDoc}
*/
public void startCalendar() {
writeStartElement("vcalendar");
}
/**
* {@inheritDoc}
*/
public void endCalendar() {
writeEndElement();
}
/**
* {@inheritDoc}
*/
public void startComponent(String name) {
writeStartElement(name);
}
/**
* {@inheritDoc}
*/
public void endComponent(String name) {
writeEndElement();
}
/**
* {@inheritDoc}
*/
public void startProperty(String name) {
writeStartElement(name);
}
/**
* {@inheritDoc}
*/
public void propertyValue(String value) throws URISyntaxException, ParseException,
IOException {
// would need unwrapping
writeCharacters(value);
}
/**
* {@inheritDoc}
*/
public void endProperty(String name) {
writeEndElement();
}
/**
* {@inheritDoc}
*/
public void parameter(String name, String value) throws URISyntaxException {
writeAttribute(name, value);
}
private void writeStartElement(String name) {
try {
xmlWriter.writeStartElement(name.toLowerCase());
} catch (XMLStreamException xe) {
throw new IllegalStateException("got xml error while writing", xe);
}
}
private void writeCharacters(String value) {
try {
xmlWriter.writeCharacters(value);
} catch (XMLStreamException xe) {
throw new IllegalStateException("got xml error while writing", xe);
}
}
private void writeAttribute(String name, String value) {
try {
xmlWriter.writeAttribute(name.toLowerCase(), value);
} catch (XMLStreamException xe) {
throw new IllegalStateException("got xml error while writing", xe);
}
}
private void writeEndElement() {
try {
xmlWriter.writeEndElement();
xmlWriter.writeCharacters("\r\n");
} catch (XMLStreamException xe) {
throw new IllegalStateException("got xml error while writing", xe);
}
}
Finally, a sample program making use of this handler, along with the program output:
public final class Main {
public static final String ICALSTREAM =
"BEGIN:VCALENDAR\r\nPRODID:-//Sun/Sample//EN\r\nVERSION:2.0\r\n" +
"BEGIN:VEVENT\r\nUID:1\r\nDTSTAMP:20070313T082041Z\r\nDTSTART;VALUE=DATE:20081212\r\n" +
"SUMMARY:wrapped \r\n" +
" summary\r\n" +
"END:VEVENT\r\nEND:VCALENDAR";
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception {
Reader reader = new UnfoldingReader(new StringReader(ICALSTREAM));
CalendarParser parser = CalendarParserFactory.getInstance().createParser();
StringWriter writer = new StringWriter();
XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
XMLHandler handler = new XMLHandler(xmlWriter);
xmlWriter.writeStartDocument();
parser.parse(reader, handler);
xmlWriter.writeEndDocument();
xmlWriter.close();
System.out.println("xml representation:" + writer.toString());
}
}
java ical2xml.Main
xml representation:<?xml version="1.0" ?><vcalendar><prodid>-//Sun/Sample//EN</prodid>
<version>2.0</version>
<vevent><uid>1</uid>
<dtstamp>20070313T082041Z</dtstamp>
<dtstart value="DATE">20081212</dtstart>
<summary>wrapped summary</summary>
</vevent>
</vcalendar>
The generated XML is totally non standard (Calconnect is currently defining such a standard) and quite ugly but it shows how the parser can be used to do lightweight processing of an iCalendar stream without going through the intermediate step of creating a full ical4j Calendar object (which can be quite expensive in terms of CPU and memory).
The same technique could be used to generate a json output or to translate an iCalendar stream into a different object model.
Posted at 10:29AM Dec 01, 2008 by arnaudq in CalDAV | Comments[0]