't Bijstere spoor

't Bijstere spoor

A blog about Web development

Converting ICalendar to XML

I've started working on a CalDAV implementation, which also requires analysis of ICalendar (rfc 2554) objects.

ICalendar objects have properties, components (such as VEVENT, VTODO) and attributes. This is awfully familiar to XML. So instead of trying to come up with a complicated parser and object structure, I decided to just convert it to XML and use PHP's simplexml.

This is my current script:

<?php

function iCalendarToXML($icalendarData) {

    
// Detecting line endings
    
if (strpos($icalendarData,"\r\n")) $lb "\r\n";
    elseif (
strpos($icalendarData,"\n")) $lb "\n";
    else 
$lb "\r\n";

    
// Splitting up items per line
    
$lines explode($lb,$icalendarData);

    
// Properties can be folded over 2 lines. In this case the second
    // line will be preceeded by a space or tab.
    
$lines2 = array();
    foreach(
$lines as $line) {

        if (
$line[0]==" " || $line[0]=="\t") {
            
$lines2[count($lines2)-1].=substr($line,1);
            continue;
        }

        
$lines2[]=$line;

    }

    
$xml '<?xml version="1.0"?>' "\n";

    
$spaces 0;
    foreach(
$lines2 as $line) {

        
$matches = array();
        
// This matches PROPERTYNAME;ATTRIBUTES:VALUE
        
if (preg_match('/^([^:^;]*)(?:;([^:]*))?:(.*)$/',$line,$matches)) {
            
$propertyName strtoupper($matches[1]);
            
$attributes $matches[2];
            
$value $matches[3];

            
// If the line was in the format BEGIN:COMPONENT or END:COMPONENT, we need to special case it.
            
if ($propertyName == 'BEGIN') {
                
$xml.=str_repeat(" ",$spaces);
                
$xml.='<' strtoupper($value) . ">\n";
                
$spaces+=2;
                continue;
            } elseif (
$propertyName == 'END') {
                
$spaces-=2;
                
$xml.=str_repeat(" ",$spaces);
                
$xml.='</' strtoupper($value) . ">\n";
                continue;
            }

            
$xml.=str_repeat(" ",$spaces);
            
$xml.='<' $propertyName;
            if (
$attributes) {
                
// There can be multiple attributes
                
$attributes explode(';',$attributes);
                foreach(
$attributes as $att) {
                    
                    list(
$attName,$attValue) = explode('=',$att,2);
                    
$xml.=' ' $attName '="' htmlspecialchars($attValue) . '"';

                }
            }

            
$xml.='>'htmlspecialchars($value) . '</' $propertyName ">\n";
          
        }

    }

    return 
$xml;

}

?>

This will convert:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VTIMEZONE
LAST-MODIFIED:20040110T032845Z
TZID:US/Eastern
BEGIN:DAYLIGHT
DTSTART:20000404T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZNAME:EDT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20001026T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:EST
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DESCRIPTION:Hello Im evert
 Next line also
  Blabla
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
DTSTAMP:20060206T001220Z
DTSTART;TZID=US/Eastern:20060104T100000
DURATION:PT1H
LAST-MODIFIED:20060206T001330Z
ORGANIZER:mailto:cyrus@example.com
SEQUENCE:1
STATUS:TENTATIVE
SUMMARY:Event #3
UID:DC6C50A017428C5216A2F1CD@example.com
X-ABC-GUID:E1CX5Dr-0007ym-Hz@example.com
END:VEVENT
END:VCALENDAR
To: <?xml version="1.0"?>
<VCALENDAR>
  <VERSION>2.0</VERSION>
  <PRODID>-//Example Corp.//CalDAV Client//EN</PRODID>
  <VTIMEZONE>
    <LAST-MODIFIED>20040110T032845Z</LAST-MODIFIED>
    <TZID>US/Eastern</TZID>
    <DAYLIGHT>
      <DTSTART>20000404T020000</DTSTART>
      <RRULE>FREQ=YEARLY;BYDAY=1SU;BYMONTH=4</RRULE>
      <TZNAME>EDT</TZNAME>
      <TZOFFSETFROM>-0500</TZOFFSETFROM>
      <TZOFFSETTO>-0400</TZOFFSETTO>
    </DAYLIGHT>
    <STANDARD>
      <DTSTART>20001026T020000</DTSTART>
      <RRULE>FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10</RRULE>
      <TZNAME>EST</TZNAME>
      <TZOFFSETFROM>-0400</TZOFFSETFROM>
      <TZOFFSETTO>-0500</TZOFFSETTO>
    </STANDARD>
  </VTIMEZONE>
  <VEVENT>
    <DESCRIPTION>Hello Im evertNext line also Blabla</DESCRIPTION>
    <ATTENDEE PARTSTAT="ACCEPTED" ROLE="CHAIR">mailto:cyrus@example.com</ATTENDEE>
    <ATTENDEE PARTSTAT="NEEDS-ACTION">mailto:lisa@example.com</ATTENDEE>
    <DTSTAMP>20060206T001220Z</DTSTAMP>
    <DTSTART TZID="US/Eastern">20060104T100000</DTSTART>
    <DURATION>PT1H</DURATION>
    <LAST-MODIFIED>20060206T001330Z</LAST-MODIFIED>
    <ORGANIZER>mailto:cyrus@example.com</ORGANIZER>
    <SEQUENCE>1</SEQUENCE>
    <STATUS>TENTATIVE</STATUS>
    <SUMMARY>Event #3</SUMMARY>
    <UID>DC6C50A017428C5216A2F1CD@example.com</UID>
    <X-ABC-GUID>E1CX5Dr-0007ym-Hz@example.com</X-ABC-GUID>
  </VEVENT>
</VCALENDAR>

I hope this is useful to anyone else.


Comments

Jan Schneider
Jan Schneider said on Saturday, 5 September 2009 at 11:30 pm CEST

Did you see http://tools.ietf.org/html/draft-royer-calsch-xcal-03?

Evert
Evert said on Thursday, 10 September 2009 at 6:18 pm CEST

Sorry for the late response. I saw this after I made this. Will post an update soon.







Solve this simple math problem to prevent bots from spamming this blog:
4 + 9 =