From 900d45a1b7928bff9c0443c34503ca89cdf165f7 Mon Sep 17 00:00:00 2001 From: znek Date: Wed, 6 Oct 2004 19:28:12 +0000 Subject: [PATCH] improved the iCal rendering git-svn-id: http://svn.opengroupware.org/SOGo/trunk@368 d1b88da0-ebda-0310-925b-ed51d893ca5b --- SOGo/SOGo.xcode/project.pbxproj | 18 ++++ SOGo/UI/Scheduler/ChangeLog | 6 ++ SOGo/UI/Scheduler/UIxAppointmentEditor.m | 51 +++++----- SOGo/UI/Scheduler/Version | 2 +- SOGoLogic/ChangeLog | 11 ++ SOGoLogic/GNUmakefile | 6 +- SOGoLogic/NSString+iCal.h | 39 ++++++++ SOGoLogic/NSString+iCal.m | 122 +++++++++++++++++++++++ SOGoLogic/SOGoAppointment.m | 2 +- SOGoLogic/SOGoAppointmentICalRenderer.m | 59 +++++------ SOGoLogic/Version | 4 +- 11 files changed, 262 insertions(+), 58 deletions(-) create mode 100644 SOGoLogic/NSString+iCal.h create mode 100644 SOGoLogic/NSString+iCal.m diff --git a/SOGo/SOGo.xcode/project.pbxproj b/SOGo/SOGo.xcode/project.pbxproj index 5b56d6cd..32da5bb2 100644 --- a/SOGo/SOGo.xcode/project.pbxproj +++ b/SOGo/SOGo.xcode/project.pbxproj @@ -568,6 +568,8 @@ AD73BE4B06CF88BF00226A2D, AD071D1206CD2BCB00A9EEF4, AD071D1306CD2BCB00A9EEF4, + ADA6333607140E0D0058C21C, + ADA6333707140E0D0058C21C, ); isa = PBXGroup; name = Classes; @@ -1113,6 +1115,22 @@ refType = 4; sourceTree = ""; }; + ADA6333607140E0D0058C21C = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = "NSString+iCal.h"; + refType = 4; + sourceTree = ""; + }; + ADA6333707140E0D0058C21C = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + path = "NSString+iCal.m"; + refType = 4; + sourceTree = ""; + }; ADCDE53106ADA8AC00BFCE2B = { fileEncoding = 5; indentWidth = 8; diff --git a/SOGo/UI/Scheduler/ChangeLog b/SOGo/UI/Scheduler/ChangeLog index a7d13b0d..f840def6 100644 --- a/SOGo/UI/Scheduler/ChangeLog +++ b/SOGo/UI/Scheduler/ChangeLog @@ -1,3 +1,9 @@ +2004-10-06 Marcus Mueller + + * UIxAppointmentEditor.m: changed iCal template to use RFC2445 + conforming line delimiters. Changed the testAction to aid in + debugging our iCal rendering. (v0.9.85) + 2004-10-05 Marcus Mueller * v0.9.84 diff --git a/SOGo/UI/Scheduler/UIxAppointmentEditor.m b/SOGo/UI/Scheduler/UIxAppointmentEditor.m index 58ca1ff0..0a3525e8 100644 --- a/SOGo/UI/Scheduler/UIxAppointmentEditor.m +++ b/SOGo/UI/Scheduler/UIxAppointmentEditor.m @@ -237,22 +237,22 @@ - (NSString *)iCalStringTemplate { static NSString *iCalStringTemplate = \ - @"BEGIN:VCALENDAR\n" - @"METHOD:REQUEST\n" - @"PRODID:OpenGroupware.org SOGo 0.9\n" - @"VERSION:2.0\n" - @"BEGIN:VEVENT\n" - @"UID:%@\n" - @"CLASS:PRIVATE\n" - @"STATUS:CONFIRMED\n" - @"DTSTAMP:%@\n" - @"DTSTART:%@\n" - @"DTEND:%@\n" - @"TRANSP:OPAQUE\n" - @"SEQUENCE:1\n" - @"PRIORITY:5\n" + @"BEGIN:VCALENDAR\r\n" + @"METHOD:REQUEST\r\n" + @"PRODID:OpenGroupware.org SOGo 0.9\r\n" + @"VERSION:2.0\r\n" + @"BEGIN:VEVENT\r\n" + @"UID:%@\r\n" + @"CLASS:PRIVATE\r\n" + @"STATUS:CONFIRMED\r\n" + @"DTSTAMP:%@\r\n" + @"DTSTART:%@\r\n" + @"DTEND:%@\r\n" + @"TRANSP:OPAQUE\r\n" + @"SEQUENCE:1\r\n" + @"PRIORITY:5\r\n" @"%@" - @"END:VEVENT\n" + @"END:VEVENT\r\n" @"END:VCALENDAR"; NSCalendarDate *lStartDate, *lEndDate; @@ -290,7 +290,7 @@ - (NSString *)iCalParticipantsStringFromQueryParameters { static NSString *iCalParticipantString = \ - @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\n"; + @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\r\n"; return [self iCalStringFromQueryParameter:@"ps" format:iCalParticipantString]; @@ -298,7 +298,7 @@ - (NSString *)iCalResourcesStringFromQueryParameters { static NSString *iCalResourceString = \ - @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\n"; + @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\r\n"; return [self iCalStringFromQueryParameter:@"rs" format:iCalResourceString]; @@ -528,15 +528,18 @@ - (id)testAction { /* for testing only */ - WORequest *req; - - NSLog(@"%s BEEN HERE!", __PRETTY_FUNCTION__); + WORequest *req; + SOGoAppointment *apt; + NSString *content; req = [[self context] request]; - NSLog(@"%@", [req formValues]); - [self logWithFormat:@"aptStartDate:%@ aptEndDate:%@", - [self aptStartDate], - [self aptEndDate]]; + apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]]; + [self saveValuesIntoAppointment:apt]; + content = [apt iCalString]; + [self logWithFormat:@"%s -- iCal:\n%@", + __PRETTY_FUNCTION__, + content]; + [apt release]; return self; } diff --git a/SOGo/UI/Scheduler/Version b/SOGo/UI/Scheduler/Version index da6507ef..dd616d8e 100644 --- a/SOGo/UI/Scheduler/Version +++ b/SOGo/UI/Scheduler/Version @@ -1,6 +1,6 @@ # $Id$ -SUBMINOR_VERSION:=84 +SUBMINOR_VERSION:=85 # v0.9.84 requires libSOGoLogic v0.9.12 # v0.9.70 requires libNGExtensions v4.3.107 diff --git a/SOGoLogic/ChangeLog b/SOGoLogic/ChangeLog index 622a8117..f753756f 100644 --- a/SOGoLogic/ChangeLog +++ b/SOGoLogic/ChangeLog @@ -1,3 +1,14 @@ +2004-10-06 Marcus Mueller + + * v0.9.13 + + * SOGoAppointmentICalRenderer.m: fixed the renderer to generate RFC2445 + conforming output. + + * NSString+iCal.[hm]: necessary utility methods for the renderer to + generate RFC2445 conforming content. + NOTE: this should eventually get moved to NGiCal. + 2004-10-05 Marcus Mueller * v0.9.12 diff --git a/SOGoLogic/GNUmakefile b/SOGoLogic/GNUmakefile index cb125a08..70483fc6 100644 --- a/SOGoLogic/GNUmakefile +++ b/SOGoLogic/GNUmakefile @@ -12,12 +12,14 @@ libSOGoLogic_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION) libSOGoLogic_HEADER_FILES += \ SOGoAppointment.h \ SOGoAppointmentICalRenderer.h \ - AgenorUserManager.h + AgenorUserManager.h \ + NSString+iCal.h libSOGoLogic_OBJC_FILES += \ SOGoAppointment.m \ SOGoAppointmentICalRenderer.m \ - AgenorUserManager.m + AgenorUserManager.m \ + NSString+iCal.m -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/library.make diff --git a/SOGoLogic/NSString+iCal.h b/SOGoLogic/NSString+iCal.h new file mode 100644 index 00000000..2d7cfdca --- /dev/null +++ b/SOGoLogic/NSString+iCal.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2000-2004 SKYRIX Software AG + + This file is part of OGo + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ +// $Id$ + + +#ifndef __NSString_iCal_H_ +#define __NSString_iCal_H_ + + +#import + + +@interface NSString (SOGoiCal) + +- (NSString *)iCalDQUOTESafeString; +- (NSString *)iCalSafeString; +- (NSString *)iCalEscapedStringWithEscapeSet:(NSCharacterSet *)_es; + +@end + +#endif /* __NSString_iCal_H_ */ diff --git a/SOGoLogic/NSString+iCal.m b/SOGoLogic/NSString+iCal.m new file mode 100644 index 00000000..8b21ed27 --- /dev/null +++ b/SOGoLogic/NSString+iCal.m @@ -0,0 +1,122 @@ +/* + Copyright (C) 2000-2004 SKYRIX Software AG + + This file is part of OGo + + OGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + OGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ +// $Id$ + + +#import "NSString+iCal.h" + + +@implementation NSString (SOGoiCal) + +#if 0 +- (NSString *)iCalFoldedString { + /* RFC2445, 4.1 Content Lines + + The iCalendar object is organized into individual lines of text, + called content lines. Content lines are delimited by a line break, + which is a CRLF sequence (US-ASCII decimal 13, followed by US-ASCII + decimal 10). + Lines of text SHOULD NOT be longer than 75 octets, excluding the line + break. Long content lines SHOULD be split into a multiple line + representations using a line "folding" technique. That is, a long + line can be split between any two characters by inserting a CRLF + immediately followed by a single linear white space character (i.e., + SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). + Any sequence of CRLF followed immediately by a single linear white space + character is ignored (i.e., removed) when processing the content type. + */ +} +#endif + +- (NSString *)iCalDQUOTESafeString { + static NSCharacterSet *escapeSet = nil; + + if(escapeSet == nil) { + escapeSet = [NSCharacterSet characterSetWithCharactersInString:@"\""]; + [escapeSet retain]; + } + return [self iCalEscapedStringWithEscapeSet:escapeSet]; +} + +- (NSString *)iCalSafeString { + static NSCharacterSet *escapeSet = nil; + + if(escapeSet == nil) { + escapeSet = [NSCharacterSet characterSetWithCharactersInString:@"\n,;\""]; + [escapeSet retain]; + } + return [self iCalEscapedStringWithEscapeSet:escapeSet]; +} + +- (NSString *)iCalEscapedStringWithEscapeSet:(NSCharacterSet *)_es { + /* Escape unsafe characters */ + NSMutableString *safeString; + NSRange r, er; + BOOL needsEscaping; + unsigned length; + + length = [self length]; + r = NSMakeRange(0, length); + er = [self rangeOfCharacterFromSet:_es options:0 range:r]; + needsEscaping = er.length > 0 ? YES : NO; + if(!needsEscaping) { + return self; /* cheap */ + } + /* wild guess */ + safeString = [NSMutableString stringWithCapacity:length + 10]; + if(needsEscaping) { + NSRange todoRange; + /* + r == previous range, upto er.location + er == escape range + todoRange == what we still need to scan + */ + length = r.length; + do { + NSString *s; + + r.length = (er.location - 1) - r.location; + s = [self substringWithRange:r]; + [safeString appendString:s]; + [safeString appendString:@"\\"]; + if([self characterAtIndex:er.location] == '\n') { + s = @"n"; + } + else { + s = [self substringWithRange:er]; + } + [safeString appendString:s]; + r.location = NSMaxRange(er); + todoRange.location = r.location; + todoRange.length = length - r.location; + er = [self rangeOfCharacterFromSet:_es + options:0 + range:todoRange]; + } + while(er.length > 0); + if(todoRange.length > 0) { + [safeString appendString:[self substringWithRange:todoRange]]; + } + } + return safeString; +} + +@end diff --git a/SOGoLogic/SOGoAppointment.m b/SOGoLogic/SOGoAppointment.m index 15f3db7a..11410330 100644 --- a/SOGoLogic/SOGoAppointment.m +++ b/SOGoLogic/SOGoAppointment.m @@ -21,10 +21,10 @@ // $Id$ #include "SOGoAppointment.h" -#include "SOGoAppointmentICalRenderer.h" #include #include #include +#include "SOGoAppointmentICalRenderer.h" #include "common.h" @interface SOGoAppointment (PrivateAPI) diff --git a/SOGoLogic/SOGoAppointmentICalRenderer.m b/SOGoLogic/SOGoAppointmentICalRenderer.m index b33c269f..57e9aa84 100644 --- a/SOGoLogic/SOGoAppointmentICalRenderer.m +++ b/SOGoLogic/SOGoAppointmentICalRenderer.m @@ -21,6 +21,7 @@ #include "SOGoAppointmentICalRenderer.h" #include "SOGoAppointment.h" +#include "NSString+iCal.h" #include #include "common.h" @@ -49,20 +50,20 @@ static SOGoAppointmentICalRenderer *renderer = nil; calendar = [_apt calendar]; - [s appendString:@"BEGIN:VCALENDAR\nMETHOD:REQUEST\n"]; + [s appendString:@"BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\n"]; [s appendString:@"PRODID:"]; [s appendString:[calendar isNotNull] ? [calendar prodId] : @"SOGo/0.9"]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; [s appendString:@"VERSION:"]; [s appendString:[calendar isNotNull] ? [calendar version] : @"2.0"]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } - (void)addPostambleForAppointment:(SOGoAppointment *)_apt toString:(NSMutableString *)s { - [s appendString:@"END:VCALENDAR\n"]; + [s appendString:@"END:VCALENDAR\r\n"]; } - (void)addOrganizer:(iCalPerson *)p toString:(NSMutableString *)s { @@ -72,14 +73,14 @@ static SOGoAppointmentICalRenderer *renderer = nil; [s appendString:@"ORGANIZER;CN=\""]; if ((x = [p cn])) - [s appendString:x]; + [s appendString:[x iCalDQUOTESafeString]]; [s appendString:@"\""]; if ((x = [p email])) { [s appendString:@":"]; /* sic! */ - [s appendString:x]; + [s appendString:[x iCalSafeString]]; } - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } - (void)addAttendees:(NSArray *)persons toString:(NSMutableString *)s { @@ -95,20 +96,20 @@ static SOGoAppointmentICalRenderer *renderer = nil; if ((x = [p role])) { [s appendString:@"ROLE="]; - [s appendString:x]; + [s appendString:[x iCalSafeString]]; [s appendString:@";"]; } [s appendString:@"CN=\""]; if ((x = [p cn])) { - [s appendString:x]; + [s appendString:[x iCalDQUOTESafeString]]; } [s appendString:@"\""]; if ([(x = [p email]) isNotNull]) { [s appendString:@":"]; /* sic! */ - [s appendString:x]; + [s appendString:[x iCalSafeString]]; } - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } } @@ -119,38 +120,38 @@ static SOGoAppointmentICalRenderer *renderer = nil; event = [_apt event]; - [s appendString:@"BEGIN:VEVENT\n"]; + [s appendString:@"BEGIN:VEVENT\r\n"]; [s appendString:@"SUMMARY:"]; - [s appendString:[_apt summary]]; - [s appendString:@"\n"]; + [s appendString:[[_apt summary] iCalSafeString]]; + [s appendString:@"\r\n"]; if ([_apt hasLocation]) { [s appendString:@"LOCATION:"]; - [s appendString:[_apt location]]; - [s appendString:@"\n"]; + [s appendString:[[_apt location] iCalSafeString]]; + [s appendString:@"\r\n"]; } [s appendString:@"UID:"]; [s appendString:[_apt uid]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; [s appendString:@"DTSTART:"]; [s appendString:[[_apt startDate] icalString]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; if ([_apt hasEndDate]) { [s appendString:@"DTEND:"]; [s appendString:[[_apt endDate] icalString]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } if ([_apt hasDuration]) { [s appendString:@"DURATION:"]; [s appendString:[event duration]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } if([_apt hasPriority]) { [s appendString:@"PRIORITY:"]; [s appendString:[_apt priority]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } if([_apt hasCategories]) { NSString *catString; @@ -158,28 +159,28 @@ static SOGoAppointmentICalRenderer *renderer = nil; catString = [[_apt categories] componentsJoinedByString:@","]; [s appendString:@"CATEGORIES:"]; [s appendString:catString]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; } if([_apt hasComment]) { - [s appendString:@"COMMENT:"]; - [s appendString:[_apt comment]]; - [s appendString:@"\n"]; + [s appendString:@"DESCRIPTION:"]; /* this is what iCal.app does */ + [s appendString:[[_apt comment] iCalSafeString]]; + [s appendString:@"\r\n"]; } [s appendString:@"STATUS:"]; [s appendString:[_apt status]]; - [s appendString:@"\n"]; + [s appendString:@"\r\n"]; /* what's all this? */ - [s appendString:@"TRANSP:OPAQUE\n"]; /* transparency */ - [s appendString:@"CLASS:PRIVATE\n"]; /* classification [like 'top secret'] */ + [s appendString:@"TRANSP:OPAQUE\r\n"]; /* transparency */ + [s appendString:@"CLASS:PRIVATE\r\n"]; /* classification [like 'top secret'] */ [self addOrganizer:[_apt organizer] toString:s]; [self addAttendees:[_apt attendees] toString:s]; /* postamble */ - [s appendString:@"END:VEVENT\n"]; + [s appendString:@"END:VEVENT\r\n"]; } - (NSString *)stringForAppointment:(SOGoAppointment *)_apt { diff --git a/SOGoLogic/Version b/SOGoLogic/Version index 9cef7993..9637b5fa 100644 --- a/SOGoLogic/Version +++ b/SOGoLogic/Version @@ -1,3 +1,5 @@ # $Id$ -SUBMINOR_VERSION:=12 +SUBMINOR_VERSION:=13 + +# v0.9.13 requires libFoundation v1.0.62 -- 2.39.5