2010-12-11 00:46:47 +03:00
/ * * * * * * B E G I N L I C E N S E B L O C K * * * * *
2008-09-18 18:13:11 +04:00
* Version : MPL 1.1 / GPL 2.0 / LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 ( the "License" ) ; you may not use this file except in compliance with
* the License . You may obtain a copy of the License at
* http : //www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis ,
* WITHOUT WARRANTY OF ANY KIND , either express or implied . See the License
* for the specific language governing rights and limitations under the
* License .
*
* The Original Code is Mozilla Calendar code .
*
* The Initial Developer of the Original Code is
* Jussi Kukkonen < jussi . kukkonen @ welho . com > .
* Portions created by the Initial Developer are Copyright ( C ) 2004
* the Initial Developer . All Rights Reserved .
*
* Contributor ( s ) :
* Michiel van Leeuwen < mvl @ exedo . nl >
* Ernst Herbst < hb @ calen . de >
2009-01-16 13:17:20 +03:00
* Philipp Kewisch < mozilla @ kewis . ch >
2008-09-18 18:13:11 +04:00
*
* Alternatively , the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later ( the "GPL" ) , or
* the GNU Lesser General Public License Version 2.1 or later ( the "LGPL" ) ,
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above . If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL , and not to allow others to
* use your version of this file under the terms of the MPL , indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL . If you do not delete
* the provisions above , a recipient may use your version of this file under
* the terms of any one of the MPL , the GPL or the LGPL .
*
* * * * * * END LICENSE BLOCK * * * * * * /
2009-01-30 19:01:19 +03:00
Components . utils . import ( "resource://calendar/modules/calAlarmUtils.jsm" ) ;
2008-09-18 18:13:11 +04:00
const localeEn = {
headTitle : "Subject" ,
headStartDate : "Start Date" ,
headStartTime : "Start Time" ,
headEndDate : "End Date" ,
headEndTime : "End Time" ,
headAllDayEvent : "All day event" ,
headAlarm : "Reminder on/off" ,
headAlarmDate : "Reminder Date" ,
headAlarmTime : "Reminder Time" ,
headCategories : "Categories" ,
headDescription : "Description" ,
headLocation : "Location" ,
headPrivate : "Private" ,
valueTrue : "True" ,
valueFalse : "False" ,
2010-12-11 00:46:47 +03:00
2008-09-18 18:13:11 +04:00
dateRe : /^(\d+)\/(\d+)\/(\d+)$/ ,
dateDayIndex : 2 ,
dateMonthIndex : 1 ,
dateYearIndex : 3 ,
dateFormat : "%m/%d/%y" ,
timeRe : /^(\d+):(\d+):(\d+) (\w+)$/ ,
timeHourIndex : 1 ,
timeMinuteIndex : 2 ,
timeSecondIndex : 3 ,
timeAmPmIndex : 4 ,
timeAmString : "AM" ,
timePmString : "PM" ,
timeFormat : "%I:%M:%S %p"
} ;
const localeNl = {
headTitle : "Onderwerp" ,
headStartDate : "Begindatum" ,
headStartTime : "Begintijd" ,
headEndDate : "Einddatum" ,
headEndTime : "Eindtijd" ,
headAllDayEvent : "Evenement, duurt hele dag" ,
headAlarm : "Herinneringen aan/uit" ,
headAlarmDate : "Herinneringsdatum" ,
headAlarmTime : "Herinneringstijd" ,
headCategories : "Categorie<69> n" ,
headDescription : "Beschrijving" ,
headLocation : "Locatie" ,
headPrivate : "Priv<69> " ,
valueTrue : "Waar" ,
valueFalse : "Onwaar" ,
2010-12-11 00:46:47 +03:00
2008-09-18 18:13:11 +04:00
dateRe : /^(\d+)-(\d+)-(\d+)$/ ,
dateDayIndex : 1 ,
dateMonthIndex : 2 ,
dateYearIndex : 3 ,
dateFormat : "%d-%m-%y" ,
timeRe : /^(\d+):(\d+):(\d+)$/ ,
timeHourIndex : 1 ,
timeMinuteIndex : 2 ,
timeSecondIndex : 3 ,
timeFormat : "%H:%M:%S"
} ;
const locales = [ localeEn , localeNl ] ;
// Windows line endings, CSV files with LF only can't be read by Outlook.
const exportLineEnding = "\r\n" ;
2010-12-11 00:46:47 +03:00
// Shared functions
function getOutlookCsvFileTypes ( aCount ) {
aCount . value = 1 ;
let wildmat = '*.csv' ;
let label = cal . calGetString ( "calendar" , 'filterOutlookCsv' , [ wildmat ] ) ;
return [ { defaultExtension : 'csv' ,
extensionFilter : wildmat ,
description : label } ] ;
}
// Importer
function calOutlookCSVImporter ( ) {
}
calOutlookCSVImporter . prototype = {
getInterfaces : function ( count ) {
const ifaces = [
Components . interfaces . nsISupports ,
Components . interfaces . nsIClassInfo ,
Components . interfaces . calIImporter ,
] ;
count . value = ifaces . length ;
return ifaces ;
} ,
getHelperForLanguage : function ( language ) {
return null ;
} ,
contractID : "@mozilla.org/calendar/import;1?type=csv" ,
classDescription : "Calendar Outlook CSV Importer" ,
classID : Components . ID ( "{64a5d17a-0497-48c5-b54f-72b15c9e9a14}" ) ,
implementationLanguage : Components . interfaces . nsIProgrammingLanguage . JAVASCRIPT ,
flags : 0 ,
QueryInterface : function QueryInterface ( aIID ) {
return cal . doQueryInterface ( this , calOutlookCSVImporter . prototype , aIID , null , this ) ;
} ,
getFileTypes : getOutlookCsvFileTypes ,
/ * *
* Takes a text block of Outlook - exported Comma Separated Values and tries to
* parse that into individual events .
*
* First line is field names , all quoted with double quotes . Field names are
* locale dependendent . In English the recognized field names are :
* "Title" , "Start Date" , "Start Time" , "End Date" , "End Time" , "All day event" ,
* "Reminder on/off" , "Reminder Date" , "Reminder Time" , "Categories" ,
* "Description" , "Location" , "Private"
* The fields "Title" and "Start Date" are mandatory . If "Start Time" misses
* the event is set as all day event . If "End Date" or "End Time" miss the
* default durations are set .
*
* The rest of the lines are events , one event per line , with fields in the
* order descibed by the first line . All non - empty values must be quoted .
*
* Returns : an array of parsed calendarEvents .
* If the parse is cancelled , a zero length array is returned .
* /
importFromStream : function csv _importFromStream ( aStream , aCount ) {
let scriptableInputStream = Components . classes [ "@mozilla.org/scriptableinputstream;1" ]
. createInstance ( Components . interfaces . nsIScriptableInputStream ) ;
scriptableInputStream . init ( aStream ) ;
let str = scriptableInputStream . read ( - 1 ) ;
parse : {
// parse header line of quoted comma separated column names.
let trimEndQuotesRegExp = /^"(.*)"$/m ;
let trimResults = trimEndQuotesRegExp . exec ( str ) ;
let header = trimResults && trimResults [ 1 ] . split ( /","/ ) ;
if ( header == null ) {
break parse ;
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
//strip header from string
str = str . slice ( trimResults [ 0 ] . length ) ;
let args = { } ;
//args.fieldList contains the field names from the first row of CSV
args . fieldList = header ;
let locale ;
let i ;
for ( i in locales ) {
locale = locales [ i ] ;
let knownIndxs = 0 ;
args . titleIndex = 0 ;
args . startDateIndex = 0 ;
for ( let i = 1 ; i <= header . length ; ++ i ) {
switch ( header [ i - 1 ] ) {
case locale . headTitle : args . titleIndex = i ; knownIndxs ++ ; break ;
case locale . headStartDate : args . startDateIndex = i ; knownIndxs ++ ; break ;
case locale . headStartTime : args . startTimeIndex = i ; knownIndxs ++ ; break ;
case locale . headEndDate : args . endDateIndex = i ; knownIndxs ++ ; break ;
case locale . headEndTime : args . endTimeIndex = i ; knownIndxs ++ ; break ;
case locale . headAllDayEvent : args . allDayIndex = i ; knownIndxs ++ ; break ;
case locale . headAlarm : args . alarmIndex = i ; knownIndxs ++ ; break ;
case locale . headAlarmDate : args . alarmDateIndex = i ; knownIndxs ++ ; break ;
case locale . headAlarmTime : args . alarmTimeIndex = i ; knownIndxs ++ ; break ;
case locale . headCategories : args . categoriesIndex = i ; knownIndxs ++ ; break ;
case locale . headDescription : args . descriptionIndex = i ; knownIndxs ++ ; break ;
case locale . headLocation : args . locationIndex = i ; knownIndxs ++ ; break ;
case locale . headPrivate : args . privateIndex = i ; knownIndxs ++ ; break ;
}
}
// Were both mandatory fields recognized?
if ( args . titleIndex != 0 && args . startDateIndex != 0 ) {
break ;
}
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
if ( knownIndxs == 0 && header . length == 22 ) {
// set default indexes for a default Outlook2000 CSV file
args . titleIndex = 1 ;
args . startDateIndex = 2 ;
args . startTimeIndex = 3 ;
args . endDateIndex = 4 ;
args . endTimeIndex = 5 ;
args . allDayIndex = 6 ;
args . alarmIndex = 7 ;
args . alarmDateIndex = 8 ;
args . alarmTimeIndex = 9 ;
args . categoriesIndex = 15 ;
args . descriptionIndex = 16 ;
args . locationIndex = 17 ;
args . privateIndex = 20 ;
}
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
if ( args . titleIndex == 0 || args . startDateIndex == 0 ) {
dump ( "Can't import. Life sucks\n" )
break parse ;
}
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
// Construct event regexp according to field indexes. The regexp can
// be made stricter, if it seems this matches too loosely.
let regExpStr = "^" ;
for ( i = 1 ; i <= header . length ; i ++ ) {
if ( i > 1 )
regExpStr += "," ;
regExpStr += "(?:\"((?:[^\"]|\"\")*)\")?" ;
}
regExpStr += "$" ;
// eventRegExp: regexp for reading events (this one'll be constructed on fly)
const eventRegExp = new RegExp ( regExpStr , "gm" ) ;
// match first line
let eventFields = eventRegExp ( str ) ;
if ( eventFields == null )
break parse ;
args . boolStr = localeEn . valueTrue ;
args . boolIsTrue = true ;
let dateParseConfirmed = false ;
let eventArray = new Array ( ) ;
do {
// At this point eventFields contains following fields. Position
// of fields is in args.[fieldname]Index.
// subject, start date, start time, end date, end time,
// all day, alarm on, alarm date, alarm time,
// Description, Categories, Location, Private
// Unused fields (could maybe be copied to Description):
// Meeting Organizer, Required Attendees, Optional Attendees,
// Meeting Resources, Billing Information, Mileage, Priority,
// Sensitivity, Show time as
let title = ( "titleIndex" in args
? this . parseTextField ( eventFields [ args . titleIndex ] ) : "" ) ;
let sDate = this . parseDateTime ( eventFields [ args . startDateIndex ] ,
eventFields [ args . startTimeIndex ] ,
locale ) ;
let eDate = this . parseDateTime ( eventFields [ args . endDateIndex ] ,
eventFields [ args . endTimeIndex ] ,
locale ) ;
// Create an event only if we have a startDate. No more checks
// on sDate needed in the following process.
if ( sDate ) {
let event = cal . createEvent ( ) ;
// Use column head in brackets if event title misses in data.
if ( title ) {
event . title = title ;
2008-09-18 18:13:11 +04:00
} else {
2010-12-11 00:46:47 +03:00
event . title = "[" + locale . headTitle + "]" ;
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
// Check data for all day event. Additionally sDate.isDate
// may have been set in parseDateTime() if no time was found
if ( eventFields [ args . allDayIndex ] == locale . valueTrue ) {
sDate . isDate = true ;
}
if ( locale . valueTrue == eventFields [ args . privateIndex ] ) {
event . privacy = "PRIVATE" ;
}
if ( ! eDate ) {
// No endDate was found. All day events last one day and
// timed events last the default length.
eDate = sDate . clone ( ) ;
if ( sDate . isDate ) {
// end date is exclusive, so set to next day after start.
2008-09-18 18:13:11 +04:00
eDate . day += 1 ;
2010-12-11 00:46:47 +03:00
} else {
eDate . minute += cal . getPrefSafe ( "calendar.event.defaultlength" , 60 ) ;
2008-09-18 18:13:11 +04:00
}
} else {
2010-12-11 00:46:47 +03:00
// An endDate was found.
if ( sDate . isDate ) {
// A time part for the startDate is missing or was
// not recognized. We have to throw away the endDates
// time part too for obtaining a valid event.
eDate . isDate = true ;
// Correct the eDate if duration is less than one day.
if ( 1 > eDate . subtractDate ( sDate ) . days ) {
eDate = sDate . clone ( ) ;
eDate . day += 1 ;
}
} else {
// We now have a timed startDate and an endDate. If the
// end time is invalid set it to 23:59:00
if ( eDate . isDate ) {
eDate . isDate = false ;
eDate . hour = 23 ;
eDate . minute = 59 ;
}
// Correct the duration to 0 seconds if it is negative.
if ( eDate . subtractDate ( sDate ) . isNegative ) {
eDate = sDate . clone ( ) ;
}
2008-09-18 18:13:11 +04:00
}
}
2010-12-11 00:46:47 +03:00
event . startDate = sDate ;
event . endDate = eDate ;
// Exists an alarm true/false column?
if ( "alarmIndex" in args ) {
// Is an alarm wanted for this event?
if ( locale . valueTrue == eventFields [ args . alarmIndex ] ) {
let alarmDate =
this . parseDateTime ( eventFields [ args . alarmDateIndex ] ,
eventFields [ args . alarmTimeIndex ] ,
locale ) ;
// Only set the alarm if a date was parsed
if ( alarmDate ) {
let alarm = cal . createAlarm ( ) ;
alarm . related = alarm . ALARM _RELATED _ABSOLUTE ;
alarm . alarmDate = alarmDate ;
event . addAlarm ( alarm ) ;
} else {
// XXX Is this really wanted here?
cal . alarms . setDefaultValues ( event ) ;
}
2008-09-18 18:13:11 +04:00
}
}
2010-12-11 00:46:47 +03:00
// Using the "Private" field only for getting privacy status.
// "Sensitivity" is neglected for now.
if ( "privateIndex" in args ) {
if ( locale . valueTrue == eventFields [ args . privateIndex ] ) {
event . privacy = "PRIVATE" ;
}
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
// Avoid setting empty properties
let txt = "" ;
if ( "descriptionIndex" in args ) {
txt = this . parseTextField ( eventFields [ args . descriptionIndex ] )
if ( txt ) {
event . setProperty ( "DESCRIPTION" , txt ) ;
}
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
if ( "categoriesIndex" in args ) {
txt = this . parseTextField ( eventFields [ args . categoriesIndex ] )
if ( txt ) {
let categories = categoriesStringToArray ( txt ) ;
event . setCategories ( categories . length , categories ) ;
}
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
if ( "locationIndex" in args ) {
txt = this . parseTextField ( eventFields [ args . locationIndex ] )
if ( txt ) {
event . setProperty ( "LOCATION" , txt ) ;
}
2008-09-18 18:13:11 +04:00
}
2010-12-11 00:46:47 +03:00
//save the event into return array
eventArray . push ( event ) ;
}
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
//get next events fields
eventFields = eventRegExp ( str ) ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
} while ( eventRegExp . lastIndex != 0 ) ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
// return results
aCount . value = eventArray . length ;
return eventArray ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
} // end parse
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
// The parse was cancelled, return an empty array of events.
aCount . value = 0 ;
return [ ] ;
} ,
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
parseDateTime : function parseDateTime ( aDate , aTime , aLocale ) {
let dt = cal . createDateTime ( ) ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
//XXX Can we do better?
dt . timezone = cal . floating ( ) ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
let rd = aLocale . dateRe . exec ( aDate ) ;
let rt = aLocale . timeRe . exec ( aTime ) ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
if ( ! rd || ! rt ) {
return null ;
}
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
dt . year = rd [ aLocale . dateYearIndex ] ;
dt . month = rd [ aLocale . dateMonthIndex ] - 1 ;
dt . day = rd [ aLocale . dateDayIndex ] ;
if ( rt ) {
dt . hour = Number ( rt [ aLocale . timeHourIndex ] ) ;
dt . minute = rt [ aLocale . timeMinuteIndex ] ;
dt . second = rt [ aLocale . timeSecondIndex ] ;
} else {
dt . isDate = true ;
}
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
if ( rt && aLocale . timeAmPmIndex &&
rt [ aLocale . timeAmPmIndex ] != aLocale . timePmString ) {
// AM
if ( dt . hour == 12 ) {
dt . hour = 0 ;
}
} else {
// PM
if ( dt . hour < 12 ) {
dt . hour += 12 ;
}
}
return dt ;
} ,
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
parseTextField : function parseTextField ( aTextField ) {
return aTextField ? aTextField . replace ( /""/g , "\"" ) : "" ;
}
} ;
2008-09-18 18:13:11 +04:00
2010-12-11 00:46:47 +03:00
// Exporter
2008-09-18 18:13:11 +04:00
function calOutlookCSVExporter ( ) {
}
2010-12-11 00:46:47 +03:00
calOutlookCSVExporter . prototype = {
getInterfaces : function ( count ) {
const ifaces = [
Components . interfaces . nsISupports ,
Components . interfaces . nsIClassInfo ,
Components . interfaces . calIExporter ,
] ;
count . value = ifaces . length ;
return ifaces ;
} ,
getHelperForLanguage : function ( language ) {
return null ;
} ,
contractID : "@mozilla.org/calendar/export;1?type=csv" ,
classDescription : "Calendar Outlook CSV Exporter" ,
classID : Components . ID ( "{48e6d3a6-b41b-4052-9ed2-40b27800bd4b}" ) ,
implementationLanguage : Components . interfaces . nsIProgrammingLanguage . JAVASCRIPT ,
flags : 0 ,
QueryInterface : function QueryInterface ( aIID ) {
return cal . doQueryInterface ( this , calOutlookCSVExporter . prototype , aIID , null , this ) ;
} ,
getFileTypes : getOutlookCsvFileTypes ,
exportToStream : function csv _exportToStream ( aStream , aCount , aItems ) {
// Helper functions
function dateString ( aDateTime ) aDateTime . jsDate . toLocaleFormat ( localeEn . dateFormat ) ;
function timeString ( aDateTime ) aDateTime . jsDate . toLocaleFormat ( localeEn . timeFormat ) ;
function txtString ( aString ) aString || "" ;
let str = "" ;
let headers = [ ] ;
// Not using a loop here, since we need to be sure the order here matches
// with the orders the field data is added later on
headers . push ( localeEn [ 'headTitle' ] ) ;
headers . push ( localeEn [ 'headStartDate' ] ) ;
headers . push ( localeEn [ 'headStartTime' ] ) ;
headers . push ( localeEn [ 'headEndDate' ] ) ;
headers . push ( localeEn [ 'headEndTime' ] ) ;
headers . push ( localeEn [ 'headAllDayEvent' ] ) ;
headers . push ( localeEn [ 'headAlarm' ] ) ;
headers . push ( localeEn [ 'headAlarmDate' ] ) ;
headers . push ( localeEn [ 'headAlarmTime' ] ) ;
headers . push ( localeEn [ 'headCategories' ] ) ;
headers . push ( localeEn [ 'headDescription' ] ) ;
headers . push ( localeEn [ 'headLocation' ] ) ;
headers . push ( localeEn [ 'headPrivate' ] ) ;
headers = headers . map ( function ( v ) {
return '"' + v + '"' ;
} ) ;
str = headers . join ( ',' ) ;
str += exportLineEnding ;
aStream . write ( str , str . length ) ;
2008-09-18 18:13:11 +04:00
2011-09-18 16:43:40 +04:00
for each ( let item in aItems ) {
2010-12-11 00:46:47 +03:00
if ( ! cal . isEvent ( item ) ) {
// XXX TODO: warn the user (once) that tasks are not supported
// (bug 336175)
continue ;
}
let line = [ ] ;
line . push ( item . title ) ;
line . push ( dateString ( item . startDate ) ) ;
line . push ( timeString ( item . startDate ) ) ;
line . push ( dateString ( item . endDate ) ) ;
line . push ( timeString ( item . endDate ) ) ;
line . push ( item . startDate . isDate ? localeEn . valueTrue : localeEn . valueFalse ) ;
let alarmDate ;
let alarms = item . getAlarms ( { } ) ;
if ( alarms . length ) {
alarmDate = cal . alarms . calculateAlarmDate ( item , alarms [ 0 ] ) ;
}
line . push ( alarmDate ? localeEn . valueTrue : localeEn . valueFalse ) ;
line . push ( alarmDate ? dateString ( alarmDate ) : "" ) ;
line . push ( alarmDate ? timeString ( alarmDate ) : "" ) ;
2011-03-06 10:54:00 +03:00
line . push ( txtString ( cal . categoriesArrayToString ( item . getCategories ( { } ) ) ) ) ; // xxx todo: what's the correct way to encode ',' in csv?, how are multi-values expressed?
2010-12-11 00:46:47 +03:00
line . push ( txtString ( item . getProperty ( "DESCRIPTION" ) ) ) ;
line . push ( txtString ( item . getProperty ( "LOCATION" ) ) ) ;
line . push ( ( item . privacy == "PRIVATE" ) ? localeEn . valueTrue : localeEn . valueFalse ) ;
line = line . map ( function ( v ) {
v = String ( v ) . replace ( /"/g , '""' ) ;
return '"' + v + '"' ;
} )
str = line . join ( ',' ) + exportLineEnding ;
aStream . write ( str , str . length ) ;
2008-09-18 18:13:11 +04:00
}
}
} ;