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.

Web mentions

Comments

  • Jan Schneider

    Jan Schneider

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

    Evert

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

    Property names (as well as parameters) are not case-sensitive. So we can have
    Begin:VEvent

    but your script does not seem to take this into account.

    Moreover, it's possible to have more than one iCalendar object in an ICS stream/string. So your script would potentially produce mal-formed XML because of multiple roots. So it's better put <root>...</root> to enclose everything immediately after

    Please refer to RFC 5545 section 3.2 "Property Parameters" and section 3.4 "iCalendar Object" (or equivalent sections in the old RFC 2445)

    • Evert

      Evert

      Make sure you take a look at sabre-vobject. This is the project I started after running into the issues you're describing, as well as a dozen others :)

      https://github.com/fruux/sa...

  • Lauren Ross

    Do you know how I can convert this ical: http://mosaic-church.onthec... to xml and then show on a seperate website as html in a table?

    • Evert

      Evert

      I'm not aware of any ready-made solutions. This may require a bit of coding. I imagine that anyone with a bit of php (or equivalent) experience would be able to do this though.

      • Lauren Ross

        Hi, Thanks for the response. I have converted the ical to xml but it shows like this: http://flourishhosting.co.u... your theory doesn't work??please help, thanks

        • Evert

          Evert

          I think you're hoping that xml will look nice by itself in the browser, but it doesn't. Even after you convert something to xml, you will still need to do work to turn it into html.

          This is not super hard, but it's simply not something that this blog post solves. Perhaps you can get further help building this in some php forum.

          • Lauren Ross

            No, I'm just hoping the xml will show as normal xml with <header>test</header> 'string' headers around it. I'm using curl to output the xml. Can you advise what I need to do to output my ical into xml like your example?

            • Lauren Ross

              Were using pre tags round the output so it should be displaying as raw xml?

              • Evert

                Evert

                No, that's now how pre tags work. If you want to display literal xml tags in a html document, you need to use something like htmlspecialchars on the output string. This converts > into &gt; and will cause it to display the way you want.

                • Lauren Ross

                  I viewed the source and found the xml.

                  • Evert

                    Evert

                    Yes it would be in the source as well :) Not sure how that would help you though.

                    • Lauren Ross

                      http://flourishhosting.co.u... How can I only show 2 strings? description and summary?

                      • Evert

                        Evert

                        Hi Lauren,

                        This question would definitely fall under 'basic php programming question'. You should either start reading books and/or tutorials, or find someone willing to teach you.

                        I can't do that through a comment box. You can hire me though.

  • J. Andreasen

    Hi. How do I execute your PHP-script? I'm rather new to PHP, and I'm learning by doing ;-)

  • Marc Van Coillie

    Hi, I'm currently using your code in a new PHP script to generate KML (Googlemap, googleearth, openstreetmap) based on ical from GoogleCalendar.
    I plan to deliver it using GNU LGLP license (lesser GPL allow to use code for both commercial and non profit while maintaining the authors and licence)
    I will mention your name and can put your email address, are you happy with that ?

  • Ghassan Safadi (Dr. Safadi)

    Tried to use URL as source of $icalendarData but no success?
    $icalendarData = file_get_contents("http......");