<?php
/**
 * A very simple un-assuming iCal parser.
 *
 * Reads a ICS file and splits the information into useable arrays.
 * Does not try and interpret the specification, just provides a raw data interface.
 *
 * @author      Aidan Lister <aidan@php.net>
 * @contributor Philipp Kewisch <mozilla@kewis.ch>
 * @version     1.5.0
 * @link        http://aidanlister.com/repos/v/iCalReader.php
 */
class iCalReader
{
    /**
     * Element array of data source
     *
     * @var     array
     * @access  private
     */
    private $_source = '';

    /**
     * Line number to be parsed next
     *
     * @var     int
     * @access  private
     */
    private $_linenum = 0;

    /**
     * Parsed data
     *
     * @var     array
     * @access  private
     */
    private $_data = array();


    /**
     * Constructor
     *
     * @param   string  Path to file to be parsed
     * @access  public
     */
    private function __construct($source)
    {
        $source = preg_split('#\n(?!\s)#', $source);
        $this->_source = array_map('trim', $source);

        $this->_data['meta'] = $this->_parseMeta();
        $this->_data['events'] = $this->_parseEvents();
    }

    static function parseString($data) {
        return new icalReader($data);
    }
    static function parseFile($fileName) {
        $data = file_get_contents($fileName);
        return new icalReader($data);
    }


    /**
     * Return parsed meta information
     *
     * @access  public
     * @return  array   Parsed meta information
     */
    public function getMeta()
    {
        return $this->_data['meta'];
    }


    /**
     * Return parsed event information
     *
     * @access  public
     * @return  array   Parsed event information
     */
    public function getEvents()
    {
        return $this->_data['events'];
    }


    /**
     * Parse meta information
     * 
     * @access  private
     */
    private function _parseMeta()
    {
        // Init
        $meta = array();
        $i = 0;
        
        // Sanity check
        if ($this->_source[$i] !== 'BEGIN:VCALENDAR') {
            return false;
        }
        
        // Iterate source
        $i++;
        while (isset($this->_source[$i])) {
            $line = $this->_source[$i];
            list($key, $value) = explode(':', $line, 2);

            // Meta information
            $meta[$key] = $value;

            $i++;

            // Check next line for EOM
            if ($this->_source[$i] === 'BEGIN:VEVENT') {
                break;
            }
        }

        $this->_linenum = $i;
        return $meta;
    }


    /**
     * Parse events information
     *
     * @access  private
     */
    private function _parseEvents()
    {
        // Init
        $events = array();
        $i = $this->_linenum;
        $j = 0;
        
        // Iterate source
        while (isset($this->_source[$i])) {   
            $line = $this->_source[$i];
            list($key, $value) = explode(':', $line, 2);

            // Event information
            $events[$j++] = $this->_parseEvent();
            $i = $this->_linenum;

            // Check next line for EOC
            if ($this->_source[$i] === 'END:VCALENDAR') {
                break;
            }
        }

        return $events;
    }


    /**
     * Parse a single events information
     *
     * @access  private
     */
    private function _parseEvent()
    {
        // Init
        $event = array();
        $i = $this->_linenum;

        // Sanity check
        if ($this->_source[$i] !== 'BEGIN:VEVENT') {
            return false;
        }

        $i++;
        while (isset($this->_source[$i])) {
            $line = $this->_source[$i];
            list($key, $value) = explode(':', $line, 2);
            $props = split(";", $key);
            $propName = array_shift($props);
            $params = array();

            foreach ($props as $p) {
                list($paramName, $paramValue) = split("=", $p);
                $params[$paramName] = $paramValue;
            }


            // Event information
            if ($propName === 'DESCRIPTION') {
                $value = str_replace(
                            array("\r\n ", '\n', '\,', '\;'),
                            array('', "\n", ',', ';'),
                            $value);
            }
            $event[$propName] = array( "params" => $params,
                                       "value" => $value);
            $i++;

            // Check next line for EOE
            if ($this->_source[$i] === 'END:VEVENT') {
                $this->_linenum = $i + 1;
                return $event;
            }
        }

        // Parsing error (End tag not found)
        return false;
    }
}

?>
