--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/OSCON/iCalReader.js Fri Jul 17 15:52:45 2009 -0700
@@ -0,0 +1,726 @@
+ /*************** START iCalReader.js ******************************************************************************/
+
+/* HEADER START
+
+ iCalendar Reader for Javascript v. 1.1
+ Parses and reads iCalendar data format.
+
+ By Per-Kristian Nordnes (pk@yammin.com), august 2007.
+ Copyright (C) 2007 - Per Kristian Nordnes.
+ All rights reserved.
+
+ The parsing algorithm in iCalendar.parse() is inspired by a PHP-script by Roman Ožana.
+
+ Changelog:
+ 1.0 - Initial release (aug. 2007).
+ 1.1 - Some fixes for parsing Google iCal-data. Thanks to Emilis Dambauskas (feb. 25, 2008)
+
+ Contents:
+
+ Class: iCalReader() - parser object.
+ Public methods:
+ * Array prepareData(String data) : set and prepare iCalendar data to read.
+ Throws exception if invalid data.
+ * void parse() : parse the data.
+ * void sort() : sort events in the calendar object.
+ * vCalendar getCalendar() : get the calendar object.
+
+ Class: vCalendar() - calendar object.
+ Public methods:
+ * Array<vEvent> getEvents() : get a array of the calendar events.
+ * Int getNrOfEvents() : get the number of events.
+ * Array<String> getPropertyNames() : get an array of the calendar's property names.
+ * PropertyMap getProperties() : get the property map object for the calendar.
+ * 0bject getProperty(String key) : get a property value by name.
+ Throws exception if property is not found (invalidPropertyException).
+ * vEvent getEventAtIndex(int index) : get an event object at given position in calendar.
+
+ Class: vEvent() - event object.
+ Public methods:
+ * Date getStartDate() : get the starting date for the event.
+ * Date getEndDate() : get the ending date for the event.
+ * String getTimezone() : get the timezone accociated with the event.
+ * PropertyMap getRuleProperties() : get a map of rules for the event.
+ * Array<String> getPropertyNames() : get an array of available property names.
+ * Object getProperty(String key) : get an event property by name.
+ Throws exception if property is not found (invalidPropertyException).
+ * String getHtmlValue(String key) : get an string property in HTML format (\n become <br/>).
+ Throws exception if property is not found (invalidPropertyException).
+
+ Class: PropertyMap() - property hashmap object for vCal and vEvent.
+ Public methods:
+ * Object getProperty(String) : get property value of property with key.
+ * void toString() : print an overview of the map's properties and values.
+ * boolean containsValue(Object value) : check if the property map contains a given value.
+ * boolean containsKey(String key) : check if the property map contains a given property name.
+ * int size() : the length of the property map.
+
+ There are also several "private" methods which are used internally by the reader/parser (but available).
+ Please refer to the code for overview/documentation on these.
+
+ License:
+ You may use this script, as long as this header is kept intact.
+ Released under the GNU General Public License Version 2 or later (the "GPL").
+
+ Usage example:
+
+ This example parses a iCalendar file and displays every property within it.
+
+ [Begin example code]
+
+ // Parse data
+
+ var myTestData = [some string content of iCalendar file or stream];
+
+ var myCalReader = new iCalReader(); // Construction of the reader object.
+ myCalReader.prepareData(myTestData); // Prepare and set the data for the parser.
+ myCalReader.parse(); // Parse the data.
+ myCalReader.sort(); // Sort the data.
+
+ // Read data
+
+ // Get the calendar properties.
+
+ var calendarInfo = 'Properties for calendar:\n\n';
+
+ var calendarPropertyNames = myCalReader.getCalendar().getPropertyNames(); // Get the list of availale properties.
+
+ for(var i=0; i<calendarPropertyNames.length; i++) { // Loop through all the properties.
+ var propertyName = calendarPropertyNames[i];
+ var propertyValue = myCalReader.getCalendar().getProperty(propertyName);
+ calendarInfo += 'Calendar property "'+propertyName+'" has value: "'+propertyValue+'"\n';
+ }
+
+ alert(calendarInfo);
+
+ // Get the event properties.
+
+ alert('About to show all events and their properties...');
+
+ var events = myCalReader.getCalendar().getEvents(); // Get all events.
+
+ for(var i=0; i<myCalReader.getCalendar().getNrOfEvents(); i++) { // Loop through all events.
+
+ var event = myCalReader.getCalendar().getEventAtIndex(i); // A single event.
+
+ // Get Javascript date for start and end time.
+ var startDate = event.getStartDate();
+ var endDate = event.getEndDate();
+ var timeZone = event.getTimeZone();
+
+ // Get rules.
+ var rules = event.getRuleProperties();
+
+ var eventInfo = 'Showing properties for event number '+(i+1)+'.\n\n'+
+ 'This event starts '+startDate+' and ends '+endDate+'\n\n'+'Timezone is: "'+timeZone+'"\n\n'+
+ 'This event have the following rules: '+rules.toString()+'\n\n';
+
+ var eventPropertyNames = event.getPropertyNames(); // Get the list of available properties.
+
+ for(var n=0; n<eventPropertyNames.length; n++) { // Loop through all the properties.
+ var propertyName = eventPropertyNames[n];
+ var propertyValue = event.getProperty(propertyName);
+ eventInfo += 'Property "'+propertyName+'" has value: "'+propertyValue+'"\n';
+ }
+
+ alert(eventInfo);
+
+ } // End for each event.
+
+ [End example code]
+
+HEADER END /*
+
+/*************** CLASS DEFINITIONS ***************************************************************************/
+
+ /**
+ * The main object for reading and parsing iCalendar data.
+ */
+function iCalReader() {
+ this.data = null; // Holds the iCalendar input data.
+ this.calendar = new vCalendar(); // The VCALENDAR object.
+ this.eventCount = -1; // Tracks the number of events in the calendar.
+ this.lastKey = null; // Reference to last proccessed key (property).
+}
+ // Class methods
+
+ iCalReader.prototype = {
+ /**
+ * Prepares and sets the data for the parser.
+ * @input calendar file data (string).
+ * @throws invalidCalendarException
+ * @return prepared data (array of lines)
+ */
+ prepareData: function(data) {
+ // Fix for malformed Mozilla VCALENDAR syntax.
+ this.data = data.replace(/[\r\n]{1,} ([:;])/g, '$1');
+ // Make array of all the lines.
+ this.data = this.data.split(/\r?\n/);
+ // Is it really a VCALENDAR?
+ if(this.data[0].indexOf('BEGIN:VCALENDAR') == -1) {
+ throw('invalidCalendarException');
+ }
+ return this.data;
+ },
+ /**
+ * Method that does the actual parsing.
+ */
+ parse: function() {
+ this.calendar = new vCalendar();
+ // Loop through all lines and analyze them.
+ for(var i=0; i<this.data.length; i++) {
+ var line = this.data[i];
+ // Get possible key/value for line.
+ var values = this.returnKeyValue(line);
+ key = values[0];
+ value = values[1];
+ switch(line) {
+ // It's a new event.
+ case 'BEGIN:VEVENT':
+ this.eventCount++;
+ type = 'VEVENT';
+ break;
+ // It's a calendar property.
+ case 'BEGIN:VCALENDAR':
+ case 'BEGIN:DAYLIGHT':
+ case 'BEGIN:VTIMEZONE':
+ case 'BEGIN:STANDARD':
+ type = value;
+ break;
+ // It's the end of the calendar property or event.
+ case 'END:VEVENT':
+ case 'END:VCALENDAR':
+ case 'END:DAYLIGHT':
+ case 'END:VTIMEZONE':
+ case 'END:STANDARD':
+ type = 'VCALENDAR';
+ break;
+ // Add data to the calendar or event.
+ default:
+ this.addToCalendar(type, key, value);
+ break;
+ }
+ }
+ },
+ /**
+ * Adds data to the calendar object from the parser.
+ * @input type of current item in parsing queue.
+ * @input key (name) of item property.
+ * @input value of item property.
+ */
+ addToCalendar: function(type, key, value) {
+ // Make a new event if we are not proccessing a current one and type is VEVENT.
+ if(type == 'VEVENT') {
+ try {
+ var event = this.calendar.getEventAtIndex(this.eventCount);
+ } catch(e) {
+ var event = new vEvent();
+ this.calendar.addEvent(event);
+ }
+ }
+ // If no key, add the current value to currently proccessing property's value.
+ if (key == false) {
+ key = this.lastKey;
+ var oldValue;
+ switch(type) {
+ case 'VEVENT':
+ oldValue = this.calendar.getEventAtIndex(this.eventCount).getProperty(key);
+ value = oldValue+this.trimStart(value);
+ break;
+ }
+ }
+ // Convert calendar date properties to javascript date.
+ if ((key == 'DTSTAMP') || (key == 'LAST-MODIFIED') || (key == 'CREATED')) {
+ value = this.toDate(value);
+ }
+ // Convert event date properties to own detailed mapping.
+ if (key.indexOf('DTSTART') > -1 || key.indexOf('DTEND') > -1|| key.indexOf('ALTDTSTART') > -1) {
+ var dateArray = this.toDateProperties(key,value);
+ key = dateArray[0];
+ value = dateArray[1];
+ }
+ // Parse any rules for item.
+ if (key == 'RRULE' ) {
+ value = this.makeRuleProperties(value);
+ }
+ // Add the data.
+ switch(type) {
+ // It's an event. Add property and value to event.
+ case 'VEVENT':
+ this.calendar.getEventAtIndex(this.eventCount).setProperty(key,value);
+ break;
+ // It's a calendar's property.
+ default:
+ this.calendar.setProperty(key,value);
+ break;
+ }
+ // Reference last proccessed key.
+ this.lastKey = key;
+ },
+ /**
+ * Make rule property map.
+ * @input string RRULE-string.
+ * @return PropertyMap of rules.
+ */
+ makeRuleProperties: function(value) {
+ var ruledata = value.split(';');
+ var rule = new PropertyMap();
+ for(var r in ruledata) {
+ var data = ruledata[r].split('=');
+ rule.put(data[0], data[1]);
+ }
+ return rule;
+ },
+ /**
+ * Parse a VEVENT type date to a own property map object.
+ * @input string property name (like "DTSTART;TZID=Europe/Oslo")
+ * @input string property value (like "20070719T220000").
+ * @return array(string DTSTART or DTEND, PropertyMap{ property JSDATE = [Date], property TZID = [string] } )
+ */
+ toDateProperties: function(key,value) {
+
+ var dtProperty = new PropertyMap();
+ dtProperty.put('TZID', 'Undefined'); // Default in case we don't find any timezone data.
+
+ // Convert time to JS-date and make property.
+ dtProperty.put('JSDATE', this.toDate(value));
+
+ // Get date info from key value.
+ var dtInfo = key.split(';');
+
+ key = dtInfo[0]; // Shorten the key to read DTSTART or DTEND not "DTSTART;TZID=Europe/Oslo".
+ dtProperty.put(key, value);
+
+ if(typeof(dtInfo[1]) != 'undefined') { // Timezone is specified.
+ // Get timezone.
+ var tzInfo = dtInfo[1].split('=');
+ var timezoneValue = tzInfo[1];
+ dtProperty.put('TZID', timezoneValue);
+ return new Array(key,dtProperty);
+ } else {
+ // Try get the calendar default TZ.
+ try { dtProperty.put('TZID', this.calendar.getProperty('TZID')); } catch(e) {}
+ try { dtProperty.put('TZID', this.calendar.getProperty('X-WR-TIMEZONE')); } catch(e) {}
+ return new Array(key,dtProperty);
+ }
+ },
+ /**
+ * Convert a iCal type timestamp to Javascript date.
+ * @input calendar type date string.
+ * @return javascript date object.
+ * @throws invalidDateException.
+ */
+ toDate: function(dateString) {
+ dateString = dateString.replace('T', '');
+ dateString = dateString.replace('Z', '');
+ var pattern = /([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})([0-9]{0,2})/;
+
+ try {
+ var dArr = pattern.exec(dateString);
+ var calDate = new Date();
+
+ var months = (dArr[2][0] == '0') ? dArr[2][1] : dArr[2];
+ months = parseInt(months)-1;
+ var days = (dArr[3][0] == '0') ? dArr[3][1] : dArr[3];
+ days = parseInt(days);
+
+ var hours = (dArr[4][0] == '0') ? dArr[4][1] : dArr[4];
+ hours = hours == '' ? '0' : hours;
+ hours = parseInt(hours);
+
+ var minutes = (dArr[5][0] == '0') ? dArr[5][1] : dArr[5];
+ minutes = minutes == '' ? '0' : minutes;
+ minutes = parseInt(minutes);
+
+ var seconds = (dArr[6][0] == '0') ? dArr[6][1] : dArr[6];
+ seconds = seconds == '' ? '0' : seconds;
+ seconds = parseInt(seconds);
+
+ calDate.setFullYear(dArr[1]);
+ calDate.setMonth(months);
+ calDate.setDate(days);
+ calDate.setHours(hours);
+ calDate.setMinutes(minutes);
+ calDate.setSeconds(seconds);
+
+ } catch(e) {
+ throw('invalidDateException');
+ }
+ return calDate;
+ },
+ /**
+ * Returns a possible value/key-set of a calendar data line.
+ * @input calendar data line (string).
+ * @return array of key,value.
+ */
+ returnKeyValue: function(line) {
+ // Regex for VCALENDAR syntax. Match letters in uppercase in the beginning
+ // of the line followed by VCALENDAR-type operator and value.
+ var pattern = /^([A-Z]+[^:]+)[:]([\w\W]*)/;
+ var matches = pattern.exec(line);
+ if(matches) {
+ return matches.splice(1,2);
+ }
+ // No key found, just return value.
+ return new Array(false,line);
+ },
+ /**
+ * Trims the beginning of string one whitespace character.
+ * @input string to trim.
+ * @return trimmed string.
+ */
+ trimStart: function(str) {
+ str=str.replace(/^\s{0,1}(.*)/, '$1');
+ return str;
+ },
+ /**
+ * Get the calendar object for the reader.
+ * @return vCalendar object.
+ */
+ getCalendar: function() {
+ return this.calendar;
+ },
+ /**
+ * Sorts the calendar events by time desc.
+ *
+ */
+ sort: function(){
+ this.calendar.sort();
+ }
+}
+
+/**
+ * Object to hold the calendar propterties.
+ */
+function vCalendar() {
+ this.vEvents = new Array();
+ this.properties = new PropertyMap();
+}
+ // Class methods
+
+ vCalendar.prototype = {
+ /**
+ * Gets the event array.
+ * @return array of event objects.
+ */
+ getEvents: function() {
+ return this.vEvents;
+ },
+ /**
+ * Gets the properties hashmap.
+ * @return PropertyMap.
+ */
+ getProperties: function() {
+ return this.properties;
+ },
+ /**
+ * Get the number of events.
+ * @return number
+ */
+ getNrOfEvents: function() {
+ return this.vEvents.length;
+ },
+ /**
+ * Sorts the array of events by time desc.
+ *
+ */
+ sort: function(){
+ this.vEvents = this.vEvents.sort(this.sortByDate);
+ },
+ /**
+ * Get list of available properties for the calendar.
+ * @return array of strings.
+ */
+ getPropertyNames: function() {
+ return this.properties.keys();
+ },
+ /**
+ * Get an event at a given index.
+ * @input int index.
+ * @return vEvent event.
+ */
+ getEventAtIndex: function(index) {
+ var evt = this.vEvents[index];
+ if(typeof(evt) == 'undefined') {
+ throw('eventNotFoundException');
+ }
+ return this.vEvents[index];
+ },
+ /**
+ * Get value of a given property.
+ * @input string property name.
+ * @return object value.
+ */
+ getProperty: function(property) {
+ try {
+ return this.properties.get(property);
+ } catch(e) {
+ throw(e);
+ }
+ },
+ /**
+ * Adds a vEvent object to the event array.
+ * @input vEvent event.
+ */
+ addEvent: function(vEvent) {
+ this.vEvents.push(vEvent);
+ },
+ /**
+ * Set a property to the calendar.
+ * @input string property name.
+ * @input object value.
+ */
+ setProperty: function(property, value) {
+ if(typeof(property) == 'string' && property != null && property != '') {
+ this.properties.put(property,value);
+ } else {
+ throw('invalidKeyNameException');
+ }
+ },
+ /**
+ * Sorting method for the events.
+ *
+ */
+ sortByDate: function(a, b) {
+ var x = a.getStartDate();
+ var y = b.getStartDate();
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ }
+ }
+
+/**
+ * Object to hold the VEVENT propterties.
+ */
+function vEvent() {
+ this.properties = new PropertyMap();
+}
+ // Class methods
+
+ vEvent.prototype = {
+ /**
+ * Get start time for event.
+ * @return Date start.
+ */
+ getStartDate: function() {
+ var dt = this.getProperty('DTSTART');
+ return dt.get('JSDATE');
+ },
+ /**
+ * Get start time for event.
+ * @return Date start.
+ */
+ getAltStartDate: function() {
+ var dt = this.getProperty('ALTDTSTART');
+ return dt.get('JSDATE');
+ },
+ /**
+ * Get end time for event.
+ * @return Date end.
+ */
+ getEndDate: function() {
+ var dt = this.getProperty('DTEND');
+ return dt.get('JSDATE');
+ },
+ /**
+ * Get timezone for event.
+ * @return string timezone.
+ */
+ getTimeZone: function() {
+ var dt = this.getProperty('DTSTART');
+ return dt.get('TZID');
+ },
+ /**
+ * Get rules for event.
+ * @return PropertyMap of rules.
+ */
+ getRuleProperties: function() {
+ var r;
+ try {
+ var r = this.getProperty('RRULE');
+ } catch(e) {
+ r = new PropertyMap();
+ }
+ return r;
+ },
+ /**
+ * Get a property by name.
+ * @input string property
+ * @return property value.
+ * @throws invalidPropertyException.
+ */
+ getProperty: function(property) {
+ try {
+ return this.removeSlashes(this.properties.get(property));
+ } catch(e) {
+ throw(e);
+ }
+ },
+ /**
+ * Sets a property with given name and value.
+ * @input string property name.
+ * @input object value.
+ */
+ setProperty: function(property, value) {
+ if(typeof(property) == 'string' && property != null && property != '') {
+ this.properties.put(property, value);
+ } else {
+ throw('invalidKeyNameException');
+ }
+ },
+ /**
+ * Get property with given key in HTML-format.
+ * @input string property name.
+ * @return HTML-string.
+ */
+ getHtmlValue: function(property) {
+ prop = this.removeSlashes(this.properties.get(property));
+ if(typeof(prop) == 'string') {
+ prop = prop.replace('\n','<br/>', 'g');
+ return prop;
+ } else {
+ return prop;
+ }
+ },
+ /**
+ * Get a list of property- names for this event.
+ * @return array of strings.
+ */
+ getPropertyNames: function() {
+ return this.properties.keys();
+ },
+ /**
+ * Removes slashes from a string
+ * @input string.
+ * @return fixed string.
+ */
+ removeSlashes: function(str) {
+ if(typeof(str) == 'string') {
+ str = str.replace('\\n','\n', 'g');
+ str = str.replace('\\,','\,', 'g');
+ str = str.replace('\\;','\;', 'g');
+ }
+ return str;
+ }
+ }
+
+/**
+ * Hashmap class to hold properties
+ * for calendar and events.
+ */
+function PropertyMap() {
+ this.size = 0;
+ this.properties = new Object();
+}
+
+ // Class methods
+
+ PropertyMap.prototype = {
+ /**
+ * Add or update property.
+ */
+ put: function(key, value) {
+ if(!this.containsKey(key)) {
+ this.size++ ;
+ }
+ this.properties[key] = value;
+ },
+ /**
+ * Get property with given key.
+ * @input property name.
+ * @return object.
+ * @throws invalidPropertyException.
+ */
+ get: function(key) {
+ if(this.containsKey(key)) {
+ return this.properties[key];
+ } else {
+ throw('invalidPropertyException');
+ }
+ },
+ /**
+ * Alias for get method to keep consistancy in syntax in regard to the other classes.
+ * @input property name.
+ * @return object.
+ * @throws invalidPropertyException.
+ */
+ getProperty: function(key) {
+ try {
+ return this.get(key);
+ } catch(e) {
+ throw(e);
+ }
+ },
+ /**
+ * Remove property with key.
+ */
+ remove: function(key) {
+ if( this.containsKey(key) && (delete this.properties[key])) {
+ size--;
+ }
+ },
+ /**
+ * Check if a property exists.
+ */
+ containsKey: function(key) {
+ return (key in this.properties);
+ },
+ /**
+ * Check if a value exists.
+ * @return boolean.
+ */
+ containsValue: function(value) {
+ for(var prop in this.properties) {
+ if(this.properties[prop] == value) {
+ return true;
+ }
+ }
+ return false;
+ },
+ /**
+ * Get all the values.
+ * @return array of values.
+ */
+ values: function () {
+ var values = new Array();
+ for(var prop in this.properties) {
+ values.push(this.properties[prop]);
+ }
+ return values;
+ },
+ /**
+ * Get all the keys.
+ * @return array of keys.
+ */
+ keys: function () {
+ var keys = new Array();
+ for(var prop in this.properties) {
+ keys.push(prop);
+ }
+ return keys;
+ },
+ /**
+ * Get the size of map.
+ * @return int size.
+ */
+ size: function () {
+ return this.size;
+ },
+ /**
+ * Clears all properties.
+ */
+ clear: function () {
+ this.size = 0;
+ this.properties = new Object();
+ },
+ /**
+ * Gives a string representation of this propertymap.
+ */
+ toString: function() {
+ var str = '';
+ for(var prop in this.properties) {
+ str += prop+'='+this.get(prop)+', ';
+ }
+ return '{ '+str.substring(0,(str.length-2))+' }';
+ }
+}
+/*************** END iCalReader.js ******************************************************************************/