Calendar Formats

In Search of an XML Calendar Format

The most popular calendar format appears to be the ICS format used by Apple’s iCal. Unfortunately, this format is not an XML format. To be able to use this data within Symphony or with XSLT it would be helpful to be able to convert the data into a useful format. However, I was disappointed to find out that the standard for ICS files is not XML format. Apple Developer Connection points to Perl and PHP scripts that can convert ICS to XML. Strangely, I did not find any XSLT templates. I probably did not search hard enough. Most likely, XSLT is not the first place people turn to for string manipulation.

I have created an XSLT template that parses the standard ICS format and transforms it into XHTML. Here is the code that parses the properties of the ICS file into a list of properties that can be displayed in XHTML. Feed the $ical-data-raw parameter with ICS data and you will get XHTML formatted properties.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output
    method="xml" 
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
    omit-xml-declaration="yes"
    encoding="UTF-8" 
    indent="yes" />

<xsl:variable name="ical-data" select="/data/vcalendar"/>
<xsl:variable name="input-data" select="normalize-space(/data/vcalendar)"/>
<xsl:variable name="ical-events">
    <xsl:value-of select="concat('BEGIN:VEVENT ',substring-before(substring-after($ical-data,'BEGIN:VEVENT'),'END:VCALENDAR'))"/>
</xsl:variable>

<xsl:template match="/">

    <h1>XSL Transform iCalendar ICS to XHTML</h1>

    <h2>iCalendar Source file</h2>
    <p><xsl:value-of select="$ical-data"/></p>



    <h2>iCalendar Data with Normalized Spaces</h2>
    <p><xsl:value-of select="$input-data"/></p>

    <h2>iCalendar Events</h2>
    <p><xsl:value-of select="$ical-events"/></p>

    <h2>Properties as XHTML</h2>
    <ul>
        <xsl:call-template name="properties"/>
    </ul>

</xsl:template>

<xsl:template name="property">
    <xsl:param name="input" select="$input-data"/>
    <xsl:param name="property" select="substring-before($input,':')"/>
    <xsl:param name="string-after-property" select="substring-after($input,':')"/>
    <xsl:param name="next-string" select="substring-before($string-after-property,':')"/>
    <xsl:param name="reverse-next-string">
        <xsl:call-template name="reverse">
            <xsl:with-param name="input" select="$next-string"/>
        </xsl:call-template>
    </xsl:param>
    <xsl:param name="reverse-next-property" select="substring-before($reverse-next-string,' ')"/>
    <xsl:param name="next-property">
        <xsl:call-template name="reverse">
            <xsl:with-param name="input" select="substring-before($reverse-next-string,' ')"/>
        </xsl:call-template>
    </xsl:param>
    <xsl:param name="value" select="substring-before($next-string, concat(' ',$next-property))"/>
    <xsl:param name="remaining-string" select="substring-after($string-after-property,concat($value,' '))"/>
    <li>Property: <xsl:value-of select="$property"/></li>
    <li>Next String: <xsl:value-of select="$next-string"/></li>
    <li>Reverse Next String: <xsl:value-of select="$reverse-next-string"/></li>
    <li>Reverse Next Property: <xsl:value-of select="$reverse-next-property"/></li>
    <li>Next Property: <xsl:value-of select="$next-property"/></li>
    <li>Value: <xsl:value-of select="$value"/></li>
    <li>Remaining String: <xsl:value-of select="$remaining-string"/></li>

    <p><xsl:value-of select="$property"/>: <xsl:value-of select="$value"/></p>
    <xsl:call-template name="next-property">
        <xsl:with-param name="input" select="$remaining-string"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="properties">
    <xsl:param name="input" select="$input-data"/>
    <xsl:param name="property" select="substring-before($input,':')"/>
    <xsl:param name="string-after-property" select="substring-after($input,':')"/>
    <xsl:param name="next-string" select="substring-before($string-after-property,':')"/>
    <xsl:param name="reverse-next-string">
        <xsl:call-template name="reverse">
            <xsl:with-param name="input" select="$next-string"/>
        </xsl:call-template>
    </xsl:param>
    <xsl:param name="reverse-next-property" select="substring-before($reverse-next-string,' ')"/>
    <xsl:param name="next-property">
        <xsl:call-template name="reverse">
            <xsl:with-param name="input" select="substring-before($reverse-next-string,' ')"/>
        </xsl:call-template>
    </xsl:param>
    <xsl:param name="value">
        <xsl:choose>
            <xsl:when test="$next-property != ''">
                <xsl:value-of select="substring-before($next-string, concat(' ',$next-property))"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$string-after-property"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:param>
    <xsl:param name="remaining-string" select="substring-after($string-after-property,concat($value,' '))"/>

    <p><xsl:value-of select="$property"/>: <xsl:value-of select="$value"/></p>
    <xsl:if test="$remaining-string != ''">
        <xsl:call-template name="properties">
            <xsl:with-param name="input" select="$remaining-string"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="reverse">
    <xsl:param name="input"/>
    <xsl:variable name="length" select="string-length($input)"/>
    <xsl:choose>
        <xsl:when test="$length &amp;lt; 2">
            <xsl:value-of select="$input"/>
        </xsl:when>     
        <xsl:when test="$length = 2">
            <xsl:value-of select="substring($input,2,1)"/>
            <xsl:value-of select="substring($input,1,1)"/>
        </xsl:when>     
        <xsl:otherwise>
            <xsl:variable name="middle" select="floor($length div 2)"/>
            <xsl:call-template name="reverse">
                <xsl:with-param name="input" select="substring($input,$middle + 1,$middle + 1)"/>
            </xsl:call-template>
            <xsl:call-template name="reverse">
                <xsl:with-param name="input" select="substring($input,1,$middle)"/>
            </xsl:call-template>
        </xsl:otherwise>        
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

I have completed building the XSLT templates that can output xCalendar. It should be a trivial matter to adjust the templates to output the hCalendar format as well (see also the W3C presentation on Semantic Web Data Integration with hCalendar and GRDDL. This will allow iCal files to be easily parsed to test the ability of the XSLT Calendar to be able to display iCal information.

The xCalendar format will be most useful as an XML data source for building XHTML pages.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE iCalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"
"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt">

<iCalendar>
    <vcalendar method="PUBLISH"
        version="2.0"
        prodid="-//HandGen//NONSGML vGen v1.0//EN">
        <vevent>
            <uid>19981116T150000@cal10.host.com</uid>
            <dtstamp>19981116T145958Z</dtstamp>
            <summary>Project XYZ Review</summary>
            <location>Conference Room 23A</location>
            <dtstart>19981116T163000Z</dtstart>
            <dtend>19981116T190000Z</dtend>
            <categories>
                <item>Appointment</item>
            </categories>
        </vevent>
    </vcalendar>
</iCalendar>

28 July 2007 by Stephen Bau