arnaudq's blog

Monday Dec 01, 2008

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.

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed

Calendar

Feeds

Search

Links

Navigation

Referrers