OSCON/iCalReader.js
changeset 7 97dcd250e5be
equal deleted inserted replaced
6:5e0dece09f96 7:97dcd250e5be
       
     1  /*************** START iCalReader.js ******************************************************************************/
       
     2 
       
     3 /* HEADER START
       
     4 
       
     5     iCalendar Reader for Javascript v. 1.1
       
     6     Parses and reads iCalendar data format.
       
     7 
       
     8     By Per-Kristian Nordnes (pk@yammin.com), august 2007.  
       
     9     Copyright (C) 2007 - Per Kristian Nordnes.
       
    10     All rights reserved.        
       
    11     
       
    12     The parsing algorithm in iCalendar.parse() is inspired by a PHP-script by Roman Ožana.
       
    13     
       
    14     Changelog:
       
    15       1.0 - Initial release (aug. 2007).
       
    16       1.1 - Some fixes for parsing Google iCal-data. Thanks to Emilis Dambauskas (feb. 25, 2008)
       
    17     
       
    18     Contents:
       
    19     
       
    20       Class: iCalReader() - parser object.
       
    21               Public methods:
       
    22                   * Array prepareData(String data) : set and prepare iCalendar data to read. 
       
    23                     Throws exception if invalid data.
       
    24                   * void parse() : parse the data.
       
    25                   * void sort()  : sort events in the calendar object.
       
    26                   * vCalendar getCalendar() : get the calendar object.                                                         
       
    27                         
       
    28       Class: vCalendar() - calendar object.
       
    29               Public methods:
       
    30                   * Array<vEvent> getEvents() : get a array of the calendar events.
       
    31                   * Int getNrOfEvents() : get the number of events.                   
       
    32                   * Array<String> getPropertyNames() : get an array of the calendar's property names.
       
    33                   * PropertyMap getProperties() : get the property map object for the calendar. 
       
    34                   * 0bject getProperty(String key) : get a property value by name.
       
    35                     Throws exception if property is not found (invalidPropertyException).
       
    36                   * vEvent getEventAtIndex(int index) : get an event object at given position in calendar.                                  
       
    37 
       
    38       Class: vEvent()  - event object.
       
    39               Public methods:
       
    40                   * Date getStartDate() : get the starting date for the event.
       
    41                   * Date getEndDate() : get the ending date for the event.
       
    42                   * String getTimezone() : get the timezone accociated with the event.
       
    43                   * PropertyMap getRuleProperties() : get a map of rules for the event.
       
    44                   * Array<String> getPropertyNames() : get an array of available property names.
       
    45                   * Object getProperty(String key) : get an event property by name.
       
    46                     Throws exception if property is not found (invalidPropertyException).
       
    47                   * String getHtmlValue(String key) : get an string property in HTML format (\n become <br/>).
       
    48                     Throws exception if property is not found (invalidPropertyException).       
       
    49       
       
    50       Class: PropertyMap() - property hashmap object for vCal and vEvent.
       
    51               Public methods:
       
    52                   * Object getProperty(String) : get property value of property with key.
       
    53                   * void toString() : print an overview of the map's properties and values.
       
    54                   * boolean containsValue(Object value) : check if the property map contains a given value.
       
    55                   * boolean containsKey(String key) : check if the property map contains a given property name.
       
    56                   * int size() : the length of the property map.
       
    57 
       
    58       There are also several "private" methods which are used internally by the reader/parser (but available).
       
    59       Please refer to the code for overview/documentation on these.                                                                                                                         
       
    60 
       
    61     License:      
       
    62       You may use this script, as long as this header is kept intact.
       
    63       Released under the GNU General Public License Version 2 or later (the "GPL").
       
    64    
       
    65     Usage example:
       
    66     
       
    67       This example parses a iCalendar file and displays every property within it.
       
    68       
       
    69       [Begin example code]
       
    70     
       
    71       // Parse data
       
    72       
       
    73           var myTestData = [some string content of iCalendar file or stream];
       
    74           
       
    75           var myCalReader = new iCalReader(); // Construction of the reader object. 
       
    76           myCalReader.prepareData(myTestData); // Prepare and set the data for the parser.
       
    77           myCalReader.parse(); // Parse the data.
       
    78           myCalReader.sort(); // Sort the data.
       
    79 
       
    80       // Read data
       
    81 
       
    82           // Get the calendar properties.            
       
    83 
       
    84               var calendarInfo = 'Properties for calendar:\n\n';                                           
       
    85 
       
    86               var calendarPropertyNames = myCalReader.getCalendar().getPropertyNames(); // Get the list of availale properties.           
       
    87 
       
    88               for(var i=0; i<calendarPropertyNames.length; i++) { // Loop through all the properties.
       
    89                 var propertyName = calendarPropertyNames[i];
       
    90                 var propertyValue = myCalReader.getCalendar().getProperty(propertyName);
       
    91                 calendarInfo += 'Calendar property "'+propertyName+'" has value: "'+propertyValue+'"\n';
       
    92               }
       
    93 
       
    94               alert(calendarInfo);
       
    95             
       
    96           // Get the event properties.
       
    97       
       
    98               alert('About to show all events and their properties...');
       
    99                                        
       
   100               var events = myCalReader.getCalendar().getEvents(); // Get all events.
       
   101                
       
   102               for(var i=0; i<myCalReader.getCalendar().getNrOfEvents(); i++) { // Loop through all events.
       
   103               
       
   104                 var event = myCalReader.getCalendar().getEventAtIndex(i); // A single event.        
       
   105                 
       
   106                 // Get Javascript date for start and end time.
       
   107                 var startDate = event.getStartDate();
       
   108                 var endDate = event.getEndDate();
       
   109                 var timeZone = event.getTimeZone();
       
   110                 
       
   111                 // Get rules.
       
   112                 var rules = event.getRuleProperties();
       
   113                 
       
   114                 var eventInfo = 'Showing properties for event number '+(i+1)+'.\n\n'+
       
   115                                 'This event starts '+startDate+' and ends '+endDate+'\n\n'+'Timezone is: "'+timeZone+'"\n\n'+
       
   116                                 'This event have the following rules: '+rules.toString()+'\n\n';                                
       
   117                 
       
   118                 var eventPropertyNames = event.getPropertyNames(); // Get the list of available properties.         
       
   119                 
       
   120                 for(var n=0; n<eventPropertyNames.length; n++) { // Loop through all the properties.                
       
   121                   var propertyName = eventPropertyNames[n];
       
   122                   var propertyValue = event.getProperty(propertyName);
       
   123                   eventInfo += 'Property "'+propertyName+'" has value: "'+propertyValue+'"\n';                  
       
   124                 }
       
   125                 
       
   126                 alert(eventInfo);
       
   127               
       
   128               } // End for each event.
       
   129           
       
   130       [End example code]    
       
   131 
       
   132 HEADER END /*
       
   133 
       
   134 /*************** CLASS DEFINITIONS ***************************************************************************/
       
   135 
       
   136  /**
       
   137  * The main object for reading and parsing iCalendar data.
       
   138  */   
       
   139 function iCalReader() {
       
   140 	this.data = null; // Holds the iCalendar input data.
       
   141 	this.calendar = new vCalendar(); // The VCALENDAR object.
       
   142 	this.eventCount = -1; // Tracks the number of events in the calendar.
       
   143 	this.lastKey = null;	// Reference to last proccessed key (property).
       
   144 }
       
   145   // Class methods
       
   146   
       
   147   iCalReader.prototype = {
       
   148   /**
       
   149    * Prepares and sets the data for the parser.
       
   150    * @input calendar file data (string).
       
   151    * @throws invalidCalendarException
       
   152    * @return prepared data (array of lines)       
       
   153    */     
       
   154       prepareData: function(data) {
       
   155         // Fix for malformed Mozilla VCALENDAR syntax.
       
   156       	this.data = data.replace(/[\r\n]{1,} ([:;])/g, '$1');
       
   157       	// Make array of all the lines.
       
   158         this.data = this.data.split(/\r?\n/);                
       
   159         // Is it really a VCALENDAR?
       
   160         if(this.data[0].indexOf('BEGIN:VCALENDAR') == -1) {
       
   161           throw('invalidCalendarException');
       
   162         }
       
   163       	return this.data;
       
   164       },
       
   165 	/**
       
   166 	 * Method that does the actual parsing.
       
   167 	 */   	
       
   168       parse: function() {
       
   169       	this.calendar = new vCalendar();
       
   170         // Loop through all lines and analyze them.
       
   171       	for(var i=0; i<this.data.length; i++) {
       
   172     			var line = this.data[i];
       
   173   			 // Get possible key/value for line.
       
   174   				var values = this.returnKeyValue(line);
       
   175   				key = values[0];
       
   176   				value = values[1];
       
   177   				switch(line) {	
       
   178   					// It's a new event.
       
   179   					case 'BEGIN:VEVENT':
       
   180   						this.eventCount++;
       
   181   						type = 'VEVENT';
       
   182   						break;
       
   183   					// It's a calendar property.
       
   184   					case 'BEGIN:VCALENDAR':
       
   185   					case 'BEGIN:DAYLIGHT':
       
   186   					case 'BEGIN:VTIMEZONE':
       
   187   					case 'BEGIN:STANDARD':
       
   188   						type = value;
       
   189   						break;
       
   190   					// It's the end of the calendar property or event.
       
   191   					case 'END:VEVENT':
       
   192   					case 'END:VCALENDAR':
       
   193   					case 'END:DAYLIGHT':
       
   194   					case 'END:VTIMEZONE':
       
   195   					case 'END:STANDARD':
       
   196   						type = 'VCALENDAR';
       
   197   						break;
       
   198   					// Add data to the calendar or event.
       
   199   					default:
       
   200   						this.addToCalendar(type, key, value);
       
   201   						break;
       
   202   				}				
       
   203         } 
       
   204       },
       
   205   /**
       
   206    * Adds data to the calendar object from the parser.
       
   207    * @input type of current item in parsing queue.
       
   208    * @input key (name) of item property.
       
   209    * @input value of item property.                     
       
   210    */     
       
   211       addToCalendar: function(type, key, value) {    
       
   212         // Make a new event if we are not proccessing a current one and type is VEVENT.
       
   213         if(type == 'VEVENT') {        
       
   214           try {
       
   215             var event = this.calendar.getEventAtIndex(this.eventCount);
       
   216           } catch(e) {
       
   217             var event = new vEvent();
       
   218             this.calendar.addEvent(event);
       
   219           }
       
   220         }    
       
   221         // If no key, add the current value to currently proccessing property's value.
       
   222     		if (key == false) {
       
   223     			key = this.lastKey;
       
   224     			var oldValue;
       
   225     			switch(type) {
       
   226     				case 'VEVENT':
       
   227     			      oldValue = this.calendar.getEventAtIndex(this.eventCount).getProperty(key); 			 
       
   228                 value = oldValue+this.trimStart(value);
       
   229                 break;
       
   230     			}
       
   231     		}
       
   232     		// Convert calendar date properties to javascript date.
       
   233     		if ((key == 'DTSTAMP') || (key == 'LAST-MODIFIED') || (key == 'CREATED')) {
       
   234     		    value = this.toDate(value);
       
   235         } 
       
   236         // Convert event date properties to own detailed mapping.
       
   237     		if (key.indexOf('DTSTART') > -1 || key.indexOf('DTEND') > -1|| key.indexOf('ALTDTSTART') > -1) {
       
   238     		    var dateArray = this.toDateProperties(key,value);
       
   239     		    key = dateArray[0];
       
   240     		    value = dateArray[1];
       
   241         }
       
   242     		// Parse any rules for item.
       
   243         if (key == 'RRULE' ) {
       
   244           value = this.makeRuleProperties(value);
       
   245         }
       
   246         // Add the data.    
       
   247         switch(type) {
       
   248           // It's an event. Add property and value to event.
       
   249     			case 'VEVENT': 
       
   250             this.calendar.getEventAtIndex(this.eventCount).setProperty(key,value);
       
   251             break;
       
   252     			// It's a calendar's property. 
       
   253     			default:
       
   254     				this.calendar.setProperty(key,value);
       
   255     				break;
       
   256     		}
       
   257         // Reference last proccessed key.
       
   258         this.lastKey = key;
       
   259     	},
       
   260   /**
       
   261    * Make rule property map.
       
   262    * @input string RRULE-string.
       
   263    * @return PropertyMap of rules.   
       
   264    */     
       
   265       makeRuleProperties: function(value) {      
       
   266         var ruledata = value.split(';');
       
   267         var rule = new PropertyMap();
       
   268         for(var r in ruledata) {
       
   269             var data = ruledata[r].split('=');
       
   270             rule.put(data[0], data[1]);
       
   271         }
       
   272         return rule;
       
   273       },    	
       
   274   /**
       
   275    * Parse a VEVENT type date to a own property map object.
       
   276    * @input string property name (like "DTSTART;TZID=Europe/Oslo")
       
   277    * @input string property value (like "20070719T220000").       
       
   278    * @return array(string DTSTART or DTEND, PropertyMap{ property JSDATE = [Date], property TZID = [string] } )   
       
   279    */     
       
   280       toDateProperties: function(key,value) {
       
   281 
       
   282           var dtProperty = new PropertyMap();
       
   283           dtProperty.put('TZID', 'Undefined'); // Default in case we don't find any timezone data.
       
   284           
       
   285           // Convert time to JS-date and make property.
       
   286           dtProperty.put('JSDATE', this.toDate(value));
       
   287 
       
   288           // Get date info from key value.
       
   289           var dtInfo = key.split(';');
       
   290           
       
   291           key = dtInfo[0]; // Shorten the key to read DTSTART or DTEND not "DTSTART;TZID=Europe/Oslo".
       
   292           dtProperty.put(key, value);
       
   293           
       
   294           if(typeof(dtInfo[1]) != 'undefined') { // Timezone is specified.
       
   295             // Get timezone.
       
   296             var tzInfo = dtInfo[1].split('=');
       
   297             var timezoneValue = tzInfo[1];
       
   298             dtProperty.put('TZID', timezoneValue);
       
   299         		return new Array(key,dtProperty);          
       
   300           } else {
       
   301             // Try get the calendar default TZ.
       
   302             try { dtProperty.put('TZID', this.calendar.getProperty('TZID')); } catch(e) {}
       
   303             try { dtProperty.put('TZID', this.calendar.getProperty('X-WR-TIMEZONE')); } catch(e) {}            
       
   304             return new Array(key,dtProperty);
       
   305         	}
       
   306       },
       
   307   /**
       
   308    * Convert a iCal type timestamp to Javascript date.
       
   309    * @input calendar type date string.
       
   310    * @return javascript date object.
       
   311    * @throws invalidDateException.     
       
   312    */     
       
   313       toDate: function(dateString) {
       
   314         dateString = dateString.replace('T', '');
       
   315         dateString = dateString.replace('Z', '');
       
   316         var pattern = /([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})([0-9]{0,2})/; 
       
   317         
       
   318         try {
       
   319           var dArr = pattern.exec(dateString);
       
   320           var calDate = new Date();
       
   321 
       
   322           var months = (dArr[2][0] == '0') ? dArr[2][1] : dArr[2];
       
   323           months = parseInt(months)-1;
       
   324           var days = (dArr[3][0] == '0') ? dArr[3][1] : dArr[3];
       
   325           days = parseInt(days);
       
   326           
       
   327           var hours = (dArr[4][0] == '0') ? dArr[4][1] : dArr[4];
       
   328           hours = hours == '' ? '0' : hours;
       
   329           hours = parseInt(hours);
       
   330           
       
   331           var minutes = (dArr[5][0] == '0') ? dArr[5][1] : dArr[5];
       
   332           minutes = minutes == '' ? '0' : minutes;
       
   333           minutes = parseInt(minutes);
       
   334           
       
   335           var seconds = (dArr[6][0] == '0') ? dArr[6][1] : dArr[6];
       
   336           seconds = seconds == '' ? '0' : seconds;
       
   337           seconds = parseInt(seconds);
       
   338           
       
   339           calDate.setFullYear(dArr[1]);
       
   340           calDate.setMonth(months);
       
   341           calDate.setDate(days);
       
   342           calDate.setHours(hours);
       
   343           calDate.setMinutes(minutes);
       
   344           calDate.setSeconds(seconds);
       
   345 
       
   346         } catch(e) {
       
   347           throw('invalidDateException');
       
   348         }
       
   349         return calDate;
       
   350       },
       
   351   /**
       
   352    * Returns a possible value/key-set of a calendar data line.
       
   353    * @input calendar data line (string).
       
   354    * @return array of key,value.       
       
   355    */     
       
   356     	returnKeyValue: function(line) {    
       
   357         // Regex for VCALENDAR syntax. Match letters in uppercase in the beginning
       
   358         // of the line followed by VCALENDAR-type operator and value.
       
   359         var pattern = /^([A-Z]+[^:]+)[:]([\w\W]*)/;
       
   360         var matches = pattern.exec(line);
       
   361         if(matches) {
       
   362               return matches.splice(1,2);
       
   363         }
       
   364         // No key found, just return value.
       
   365         return new Array(false,line);
       
   366     	},
       
   367   /**
       
   368    * Trims the beginning of string one whitespace character.
       
   369    * @input string to trim.
       
   370    * @return trimmed string.          
       
   371    */     
       
   372       trimStart: function(str) {
       
   373         str=str.replace(/^\s{0,1}(.*)/, '$1');
       
   374         return str;
       
   375       },            
       
   376 	/**
       
   377 	 * Get the calendar object for the reader.
       
   378 	 * @return vCalendar object.    	 
       
   379 	 */   	
       
   380     	getCalendar: function() {
       
   381     		return this.calendar;
       
   382     	},
       
   383   /**
       
   384    * Sorts the calendar events by time desc.   
       
   385    *
       
   386    */
       
   387       sort: function(){
       
   388         this.calendar.sort();
       
   389       }     
       
   390 }
       
   391 
       
   392 /**
       
   393  * Object to hold the calendar propterties.
       
   394  */   
       
   395 function vCalendar() {
       
   396     this.vEvents = new Array();
       
   397     this.properties = new PropertyMap();
       
   398 }
       
   399     // Class methods
       
   400     
       
   401   vCalendar.prototype = {
       
   402   /**
       
   403    * Gets the event array.
       
   404    * @return array of event objects.       
       
   405    */     
       
   406       getEvents: function() {
       
   407         return this.vEvents;
       
   408       },
       
   409   /**
       
   410    * Gets the properties hashmap.
       
   411    * @return PropertyMap.       
       
   412    */     
       
   413       getProperties: function() {
       
   414         return this.properties;
       
   415       },
       
   416   /**
       
   417    * Get the number of events.
       
   418    * @return number   
       
   419    */        
       
   420       getNrOfEvents: function() {
       
   421         return this.vEvents.length;
       
   422       },
       
   423   /**
       
   424    * Sorts the array of events by time desc.   
       
   425    *
       
   426    */
       
   427       sort: function(){
       
   428         this.vEvents = this.vEvents.sort(this.sortByDate);
       
   429       },        
       
   430   /**
       
   431    * Get list of available properties for the calendar.
       
   432    * @return array of strings.       
       
   433    */     
       
   434       getPropertyNames: function() {
       
   435         return this.properties.keys();
       
   436       },
       
   437   /**
       
   438    * Get an event at a given index.
       
   439    * @input int index.
       
   440    * @return vEvent event.              
       
   441    */
       
   442       getEventAtIndex: function(index) {
       
   443         var evt = this.vEvents[index];
       
   444         if(typeof(evt) == 'undefined') {
       
   445           throw('eventNotFoundException');
       
   446         }
       
   447         return this.vEvents[index];
       
   448       },
       
   449   /**
       
   450    * Get value of a given property.
       
   451    * @input string property name.
       
   452    * @return object value.         
       
   453    */
       
   454       getProperty: function(property) {  
       
   455         try {
       
   456           return this.properties.get(property);      
       
   457         } catch(e) {
       
   458           throw(e); 
       
   459         }
       
   460       },
       
   461   /**
       
   462    * Adds a vEvent object to the event array.
       
   463    * @input vEvent event.       
       
   464    */
       
   465       addEvent: function(vEvent) {
       
   466         this.vEvents.push(vEvent);
       
   467       },
       
   468   /**
       
   469    * Set a property to the calendar.
       
   470    * @input string property name.
       
   471    * @input object value.              
       
   472    */
       
   473       setProperty: function(property, value) {
       
   474         if(typeof(property) == 'string' && property != null && property != '') {      
       
   475           this.properties.put(property,value);
       
   476         } else {
       
   477           throw('invalidKeyNameException');
       
   478         }
       
   479       },
       
   480   /**
       
   481    * Sorting method for the events.
       
   482    *
       
   483    */
       
   484       sortByDate: function(a, b) {
       
   485           var x = a.getStartDate();
       
   486           var y = b.getStartDate();
       
   487           return ((x < y) ? -1 : ((x > y) ? 1 : 0));
       
   488       }      
       
   489   }
       
   490 
       
   491 /**
       
   492  * Object to hold the VEVENT propterties.
       
   493  */   
       
   494 function vEvent() {
       
   495   this.properties = new PropertyMap();
       
   496 }
       
   497   // Class methods
       
   498   
       
   499   vEvent.prototype = {
       
   500     /**
       
   501      * Get start time for event.
       
   502      * @return Date start.         
       
   503      */
       
   504         getStartDate: function() {
       
   505            var dt = this.getProperty('DTSTART');
       
   506            return dt.get('JSDATE');
       
   507         },
       
   508     /**
       
   509      * Get start time for event.
       
   510      * @return Date start.         
       
   511      */
       
   512         getAltStartDate: function() {
       
   513            var dt = this.getProperty('ALTDTSTART');
       
   514            return dt.get('JSDATE');
       
   515         },
       
   516     /**
       
   517      * Get end time for event.
       
   518      * @return Date end.              
       
   519      */
       
   520         getEndDate: function() {
       
   521            var dt = this.getProperty('DTEND');
       
   522            return dt.get('JSDATE');
       
   523         },
       
   524     /**
       
   525      * Get timezone for event.
       
   526      * @return string timezone.              
       
   527      */
       
   528         getTimeZone: function() {
       
   529            var dt = this.getProperty('DTSTART');
       
   530            return dt.get('TZID');
       
   531         },
       
   532     /**
       
   533      * Get rules for event.
       
   534      * @return PropertyMap of rules.              
       
   535      */
       
   536         getRuleProperties: function() {
       
   537             var r;
       
   538             try {
       
   539               var r = this.getProperty('RRULE');
       
   540             } catch(e) {
       
   541               r = new PropertyMap();
       
   542             }
       
   543             return r;
       
   544         },
       
   545     /**
       
   546      * Get a property by name.
       
   547      * @input string property
       
   548      * @return property value.
       
   549      * @throws invalidPropertyException.         
       
   550      */     
       
   551         getProperty: function(property) {  
       
   552           try {
       
   553             return this.removeSlashes(this.properties.get(property));      
       
   554           } catch(e) {
       
   555             throw(e); 
       
   556           }
       
   557         },
       
   558     /**
       
   559      * Sets a property with given name and value.
       
   560      * @input string property name.
       
   561      * @input object value.              
       
   562      */
       
   563         setProperty: function(property, value) {
       
   564             if(typeof(property) == 'string' && property != null && property != '') {
       
   565               this.properties.put(property, value);
       
   566             } else {
       
   567               throw('invalidKeyNameException');
       
   568             }
       
   569         },
       
   570     /**
       
   571      * Get property with given key in HTML-format.
       
   572      * @input string property name.       
       
   573      * @return HTML-string.       
       
   574      */
       
   575         getHtmlValue: function(property) {
       
   576           prop = this.removeSlashes(this.properties.get(property));
       
   577           if(typeof(prop) == 'string') {
       
   578             prop = prop.replace('\n','<br/>', 'g');
       
   579             return prop; 
       
   580           } else {
       
   581             return prop;
       
   582           }                    
       
   583         },
       
   584     /**
       
   585      * Get a list of property- names for this event.
       
   586      * @return array of strings.       
       
   587      */     
       
   588         getPropertyNames: function() {
       
   589           return this.properties.keys();
       
   590         },
       
   591     /**
       
   592      * Removes slashes from a string
       
   593      * @input string.       
       
   594      * @return fixed string.       
       
   595      */  
       
   596         removeSlashes: function(str) {
       
   597             if(typeof(str) == 'string') {
       
   598               str = str.replace('\\n','\n', 'g');
       
   599               str = str.replace('\\,','\,', 'g');
       
   600               str = str.replace('\\;','\;', 'g');
       
   601             }    
       
   602             return str;
       
   603         }  
       
   604   }
       
   605 
       
   606 /**
       
   607  * Hashmap class to hold properties
       
   608  * for calendar and events.   
       
   609  */ 
       
   610 function PropertyMap()  {   
       
   611   this.size = 0;   
       
   612   this.properties = new Object();
       
   613 }
       
   614 
       
   615   // Class methods
       
   616   
       
   617   PropertyMap.prototype = {
       
   618     /**
       
   619      * Add or update property.
       
   620      */            
       
   621         put: function(key, value) {   
       
   622           if(!this.containsKey(key)) {   
       
   623               this.size++ ;   
       
   624           }   
       
   625           this.properties[key] = value;   
       
   626         },   
       
   627     /**
       
   628      * Get property with given key.
       
   629      * @input property name.     
       
   630      * @return object.
       
   631      * @throws invalidPropertyException.
       
   632      */            
       
   633         get: function(key) {
       
   634           if(this.containsKey(key)) {
       
   635             return this.properties[key];
       
   636           } else {
       
   637             throw('invalidPropertyException');
       
   638           }   
       
   639         },
       
   640     /**
       
   641      * Alias for get method to keep consistancy in syntax in regard to the other classes.
       
   642      * @input property name.     
       
   643      * @return object.
       
   644      * @throws invalidPropertyException.
       
   645      */            
       
   646         getProperty: function(key) {
       
   647           try {
       
   648             return this.get(key);
       
   649           } catch(e) {
       
   650             throw(e);
       
   651           }
       
   652         },
       
   653     /**
       
   654      * Remove property with key.
       
   655      */
       
   656         remove: function(key) {   
       
   657           if( this.containsKey(key) && (delete this.properties[key])) {   
       
   658               size--;   
       
   659           } 
       
   660         },   
       
   661     /**
       
   662      * Check if a property exists.
       
   663      */
       
   664         containsKey: function(key) {   
       
   665           return (key in this.properties);   
       
   666         },    
       
   667     /**
       
   668      * Check if a value exists.
       
   669      * @return boolean.     
       
   670      */
       
   671         containsValue: function(value) {   
       
   672           for(var prop in this.properties) {   
       
   673               if(this.properties[prop] == value) {   
       
   674                   return true; 
       
   675               }   
       
   676           }   
       
   677           return false;   
       
   678         },   
       
   679     /**
       
   680      * Get all the values.
       
   681      * @return array of values.     
       
   682      */
       
   683         values: function () {   
       
   684           var values = new Array();   
       
   685           for(var prop in this.properties) {   
       
   686               values.push(this.properties[prop]);   
       
   687           }   
       
   688           return values;   
       
   689         },
       
   690     /**
       
   691      * Get all the keys.
       
   692      * @return array of keys.     
       
   693      */
       
   694         keys: function () {   
       
   695           var keys = new Array();   
       
   696           for(var prop in this.properties) {   
       
   697               keys.push(prop);   
       
   698           }   
       
   699           return keys;   
       
   700         },
       
   701     /**
       
   702      * Get the size of map.
       
   703      * @return int size.     
       
   704      */
       
   705         size: function () {   
       
   706           return this.size;   
       
   707         },   
       
   708     /**
       
   709      * Clears all properties.
       
   710      */
       
   711         clear: function () {   
       
   712           this.size = 0;   
       
   713           this.properties = new Object();   
       
   714         },
       
   715     /**
       
   716      * Gives a string representation of this propertymap.
       
   717      */         
       
   718         toString: function() {
       
   719             var str = '';
       
   720             for(var prop in this.properties) {   
       
   721                 str += prop+'='+this.get(prop)+', ';   
       
   722             }        
       
   723             return '{ '+str.substring(0,(str.length-2))+' }'; 
       
   724         }
       
   725 }   
       
   726 /*************** END iCalReader.js ******************************************************************************/