Renamed SG_iCalReader to SG_iCal. Moved the parsing to a separate class.

master
fangel 2009-01-10 11:10:45 +00:00
rodzic f702b4a79b
commit 4a0982f9fd
6 zmienionych plików z 203 dodań i 212 usunięć

Wyświetl plik

@ -1,11 +1,9 @@
<?php
define('SG_ICALREADER_VERSION', '0.3');
define('SG_ICALREADER_VERSION', '0.5');
/**
* A simple iCalReader. Won't handle all the different fancy-smancy
* stuff. What it will do is to parse the END/BEGIN-structures into
* a easy to parse/read php-array.
* A simple iCal parser. Should take care of most stuff for ya
*
* Roadmap:
* * Finish FREQUENCY-parsing.
@ -13,7 +11,7 @@ define('SG_ICALREADER_VERSION', '0.3');
*
* A simple example:
* <?php
* $ical = new SG_iCalReader("http://example.com/calendar.ics");
* $ical = new SG_iCal("http://example.com/calendar.ics");
* foreach( $ical->getEvents() As $event ) {
* // Do stuff with the event $event
* }
@ -23,59 +21,54 @@ define('SG_ICALREADER_VERSION', '0.3');
* @author Morten Fangel (C) 2008
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
*/
class SG_iCalReader {
private $url;
private $parsed;
private $is_utf8 = true;
private $parsed_data = array();
class SG_iCal {
private $information;
private $events;
private $timezones;
/**
* Constructs a new iCalReader. You can supply the url now, or later using setUrl
* @param $url string
*/
public function __construct($url = '') {
$this->url = $url;
$this->parsed = false;
public function __construct($url = false) {
require_once dirname(__FILE__) . '/helpers/SG_iCal_Factory.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Line.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Query.php'; // BUILD: Remove line
require_once dirname(__FILE__) . '/helpers/SG_iCal_Parser.php'; // BUILD: Remove line
if( $url !== false ) {
SG_iCal_Parser::Parse($url, $this);
}
}
/**
* Sets (or resets) the url this reader reads from. If the given
* url is empty or the same as the existing it will be ignored.
* Otherwise the url will change and the status of the parser will
* be reset.
*
* If you need to time a few passes over parse(), a quick way to
* make sure _parse() will get called is to set the url again
* and setting $force to true to reset the url even though it even
* though the url hasn't changed.
*
* Sets (or resets) the url this reader reads from.
* @param $url string
* @param $force bool
*/
public function setUrl( $url = '', $force = false ) {
if( trim($url) != '' && ($url != $this->url || $force) ) {
$this->url = $url;
$this->parsed = false;
$this->is_utf8 = true;
public function setUrl( $url = false ) {
if( $url !== false ) {
SG_iCal_Parser::Parse($url, $this);
}
}
/**
* Returns the main calendar info. You can then query the returned
* object with ie getTitle().
*
* @return SG_iCal_VCalendar
*/
public function getCalendarInfo() {
$this->ensureParsed();
return $this->parsed_data['vcalendar'];
return $this->information;
}
/**
* Sets the calendar info for this calendar
* @param SG_iCal_VCalendar $info
*/
public function setCalendarInfo( SG_iCal_VCalendar $info ) {
$this->information = $info;
}
/**
* Returns a given timezone for the calendar. This is mainly used
* by VEvents to adjust their date-times if they have specified a
@ -88,11 +81,10 @@ class SG_iCalReader {
* @return SG_iCal_VTimeZone
*/
public function getTimeZoneInfo( $tzid = null ) {
$this->ensureParsed();
if( $tzid == null ) {
return $this->parsed_data['vtimezone'];
return $this->timezones;
} else {
foreach( $this->parsed_data['vtimezone'] AS $tz ) {
foreach( $this->timezones AS $tz ) {
if( $tz->getTimeZoneId() == $tzid ) {
return $tz;
}
@ -101,186 +93,32 @@ class SG_iCalReader {
}
}
/**
* Adds a new timezone to this calendar
* @param SG_iCal_VTimeZone $tz
*/
public function addTimeZone( SG_iCal_VTimeZone $tz ) {
$this->timezones[] = $tz;
}
/**
* Returns the events found
* @return array
*/
public function getEvents() {
$this->ensureParsed();
return $this->parsed_data['vevent'];
return $this->events;
}
/**
* Makes sure the iCal file gets fetched, decoded, unfolded and
* parsed.
*
* Note: It's not neccesary to call parse yourself. If you query
* for some of the data, the file will be parsed.
* It can't hurt, as a already parsed feed ain't parsed again..
*
*/
public function parse() {
if( !$this->parsed ) {
$content = $this->fetch();
if( !$this->is_utf8 ) {
$content = utf8_encode($content);
}
$content = $this->unfold_lines($content);
$this->_parse( $content );
$this->parsed = true;
}
}
// The rest are private function
/**
* Ensures that the current url is parsed in the parsed_data member
* @throws Exception
* @ensure The feed located at $url is parsed and stored in $parsed_data
*/
private function ensureParsed() {
if( !$this->parsed ) {
if( $this->url == '' ) {
throw new Exception('No url given to iCalReader to parse');
}
$this->parse();
}
if( empty($this->parsed_data) ) {
throw new Exception('Invalid iCal file or parse failure!');
}
}
/**
* Fetches url and stores it in the content member
* @return string
*/
protected function fetch() {
$c = curl_init();
curl_setopt($c, CURLOPT_URL, $this->url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
if( !ini_get('safe_mode') ){
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
}
$content = curl_exec($c);
$ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);
$enc = preg_replace('/^.*charset=([-a-zA-Z0-9]+).*$/', '$1', $ct);
if( $ct != '' && strtolower(str_replace('-','', $enc)) != 'utf8' ) {
// Well, the encoding says it ain't utf-8
$this->is_utf8 = false;
} elseif( !$this->_valid_utf8( $content ) ) {
// The data isn't utf-8
$this->is_utf8 = false;
}
return $content;
}
/**
* Takes the string $content, and creates a array of iCal lines.
* This includes unfolding multi-line entries into a single line.
* @param $content string
*/
private function unfold_lines($content) {
$data = array();
$content = explode("\n", $content);
for( $i=0; $i < count($content); $i++) {
$line = rtrim($content[$i]);
while( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == ' ' || $content[$i+1]{0} == "\t" )) {
$line .= rtrim(substr($content[++$i],1));
}
$data[] = $line;
}
return $data;
}
/**
* Parses the feed found in content and calls storeSection to store
* parsed data
*/
private function _parse( $content ) {
$main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
$sections = array();
$section = '';
$current_data = array();
foreach( $content AS $line ) {
$line = new SG_iCal_Line($line);
if( $line->isBegin() ) {
// New block of data, $section = new block
$section = strtolower($line->getData());
$sections[] = strtolower($line->getData());
} elseif( $line->isEnd() ) {
// End of block of data ($removed = just ended block, $section = new top-block)
$removed = array_pop($sections);
$section = end($sections);
if( array_search($removed, $main_sections) !== false ) {
$this->storeSection( $removed, $current_data[$removed]);
$current_data[$removed] = array();
}
} else {
// Data line
foreach( $main_sections AS $s ) {
// Loops though the main sections
if( array_search($s, $sections) !== false ) {
// This section is in the main section
if( $section == $s ) {
// It _is_ the main section
$current_data[$s][$line->getIdent()] = $line;
} else {
// Sub section
$current_data[$s][$section][$line->getIdent()] = $line;
}
break;
}
}
}
}
$current_data = array();
}
/**
* Stores the data in the parsed_data member
* Adds a event to this calendar
* @param SG_iCal_VEvent $event
*/
private function storeSection( $section, $data ) {
$data = SG_iCal_Factory::factory($this, $section, $data);
if( $section == 'vcalendar' ) {
// We don't want to do parsed_data['vcalendar'][0] to get the data, so it's special..
$this->parsed_data['vcalendar'] = $data;
} else {
$this->parsed_data[$section][] = $data;
}
}
/**
* This functions does some regexp checking to see if the value is
* valid UTF-8.
*
* The function is from the book "Building Scalable Web Sites" by
* Cal Henderson.
*
* @return bool
*/
private function _valid_utf8( $data ) {
$rx = '[\xC0-\xDF]([^\x80-\xBF]|$)';
$rx .= '|[\xE0-\xEF].{0,1}([^\x80-\xBF]|$)';
$rx .= '|[\xF0-\xF7].{0,2}([^\x80-\xBF]|$)';
$rx .= '|[\xF8-\xFB].{0,3}([^\x80-\xBF]|$)';
$rx .= '|[\xFC-\xFD].{0,4}([^\x80-\xBF]|$)';
$rx .= '|[\xFE-\xFE].{0,5}([^\x80-\xBF]|$)';
$rx .= '|[\x00-\x7F][\x80-\xBF]';
$rx .= '|[\xC0-\xDF].[\x80-\xBF]';
$rx .= '|[\xE0-\xEF]..[\x80-\xBF]';
$rx .= '|[\xF0-\xF7]...[\x80-\xBF]';
$rx .= '|[\xF8-\xFB]....[\x80-\xBF]';
$rx .= '|[\xFC-\xFD].....[\x80-\xBF]';
$rx .= '|[\xFE-\xFE]......[\x80-\xBF]';
$rx .= '|^[\x80-\xBF]';
return ( ! (bool) preg_match('!'.$rx.'!', $data) );
public function addEvent( SG_iCal_VEvent $event ) {
$this->events[] = $event;
}
}
/**
* For legacy reasons, we keep the name SG_iCalReader..
*/
class SG_iCalReader extends SG_iCal {}

Wyświetl plik

@ -29,7 +29,7 @@ class SG_iCal_VEvent {
* @param SG_iCal_Line[] $data
* @param SG_iCalReader $ical
*/
public function __construct($data, SG_iCalReader $ical ) {
public function __construct($data, SG_iCal $ical ) {
$this->uid = $data['uid']->getData();
unset($data['uid']);

Wyświetl plik

@ -70,7 +70,7 @@ class SG_iCal_VTimeZone {
}
$daylight_freq = new SG_iCal_Freq($this->daylight['rrule'], strtotime($this->daylight['dtstart']));
$last_dst = $daylight_freq->lastOccurance($ts);
$last_dst = $daylight_freq->lastOccurrence($ts);
if( date('Y') == date('Y', $last_dst) ) {
$this->cache[$ts] = 'daylight';
} else {

Wyświetl plik

@ -23,7 +23,7 @@ class SG_iCal_Factory {
* @param $section string
* @param SG_iCal_Line[]
*/
public static function factory( SG_iCalReader $ical, $section, $data ) {
public static function factory( SG_iCal $ical, $section, $data ) {
switch( $section ) {
case "vcalendar":
require_once dirname(__FILE__).'/../blocks/SG_iCal_VCalendar.php'; // BUILD: Remove line

Wyświetl plik

@ -0,0 +1,152 @@
<?php
class SG_iCal_Parser {
public static function Parse( $url, SG_iCal $ical ) {
$content = self::Fetch( $url );
$content = self::UnfoldLines($content);
self::_Parse( $content, $ical );
}
/**
* Fetches url and stores it in the content member
* @return string
*/
protected static function Fetch( $url ) {
$c = curl_init();
$is_utf8 = true;
curl_setopt($c, CURLOPT_URL, $url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
if( !ini_get('safe_mode') ){
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
}
$content = curl_exec($c);
$ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);
$enc = preg_replace('/^.*charset=([-a-zA-Z0-9]+).*$/', '$1', $ct);
if( $ct != '' && strtolower(str_replace('-','', $enc)) != 'utf8' ) {
// Well, the encoding says it ain't utf-8
$is_utf8 = false;
} elseif( ! self::_ValidUtf8( $content ) ) {
// The data isn't utf-8
$is_utf8 = false;
}
if( !$is_utf8 ) {
$content = utf8_encode($content);
}
return $content;
}
/**
* Takes the string $content, and creates a array of iCal lines.
* This includes unfolding multi-line entries into a single line.
* @param $content string
*/
protected static function UnfoldLines($content) {
$data = array();
$content = explode("\n", $content);
for( $i=0; $i < count($content); $i++) {
$line = rtrim($content[$i]);
while( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == ' ' || $content[$i+1]{0} == "\t" )) {
$line .= rtrim(substr($content[++$i],1));
}
$data[] = $line;
}
return $data;
}
/**
* Parses the feed found in content and calls storeSection to store
* parsed data
*/
private static function _Parse( $content, SG_iCal $ical ) {
$main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
$sections = array();
$section = '';
$current_data = array();
foreach( $content AS $line ) {
$line = new SG_iCal_Line($line);
if( $line->isBegin() ) {
// New block of data, $section = new block
$section = strtolower($line->getData());
$sections[] = strtolower($line->getData());
} elseif( $line->isEnd() ) {
// End of block of data ($removed = just ended block, $section = new top-block)
$removed = array_pop($sections);
$section = end($sections);
if( array_search($removed, $main_sections) !== false ) {
self::StoreSection( $removed, $current_data[$removed], $ical);
$current_data[$removed] = array();
}
} else {
// Data line
foreach( $main_sections AS $s ) {
// Loops though the main sections
if( array_search($s, $sections) !== false ) {
// This section is in the main section
if( $section == $s ) {
// It _is_ the main section
$current_data[$s][$line->getIdent()] = $line;
} else {
// Sub section
$current_data[$s][$section][$line->getIdent()] = $line;
}
break;
}
}
}
}
$current_data = array();
}
/**
* Stores the data in the parsed_data member
*/
protected static function storeSection( $section, $data, SG_iCal $ical ) {
$data = SG_iCal_Factory::Factory($ical, $section, $data);
switch( $section ) {
case 'vcalendar':
return $ical->setCalendarInfo( $data );
case 'vevent':
return $ical->addEvent( $data );
case 'vjournal':
case 'vtodo':
return true; // TODO: Implement
case 'vtimezone':
return $ical->addTimeZone( $data );
}
}
/**
* This functions does some regexp checking to see if the value is
* valid UTF-8.
*
* The function is from the book "Building Scalable Web Sites" by
* Cal Henderson.
*
* @return bool
*/
private static function _ValidUtf8( $data ) {
$rx = '[\xC0-\xDF]([^\x80-\xBF]|$)';
$rx .= '|[\xE0-\xEF].{0,1}([^\x80-\xBF]|$)';
$rx .= '|[\xF0-\xF7].{0,2}([^\x80-\xBF]|$)';
$rx .= '|[\xF8-\xFB].{0,3}([^\x80-\xBF]|$)';
$rx .= '|[\xFC-\xFD].{0,4}([^\x80-\xBF]|$)';
$rx .= '|[\xFE-\xFE].{0,5}([^\x80-\xBF]|$)';
$rx .= '|[\x00-\x7F][\x80-\xBF]';
$rx .= '|[\xC0-\xDF].[\x80-\xBF]';
$rx .= '|[\xE0-\xEF]..[\x80-\xBF]';
$rx .= '|[\xF0-\xF7]...[\x80-\xBF]';
$rx .= '|[\xF8-\xFB]....[\x80-\xBF]';
$rx .= '|[\xFC-\xFD].....[\x80-\xBF]';
$rx .= '|[\xFE-\xFE]......[\x80-\xBF]';
$rx .= '|^[\x80-\xBF]';
return ( ! (bool) preg_match('!'.$rx.'!', $data) );
}
}

Wyświetl plik

@ -10,6 +10,7 @@ require_once dirname(__FILE__) . "/../helpers/SG_iCal_Duration.php";
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Factory.php";
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Freq.php";
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Line.php";
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Parser.php";
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Query.php";
?>