2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "iCalRenderer.h"
23 #include "iCalEvent.h"
24 #include "iCalPerson.h"
27 @interface NSDate(UsedPrivates)
28 - (NSString *)icalString; // declared in NGiCal
31 @implementation iCalRenderer
33 static iCalRenderer *renderer = nil;
35 /* assume length of 1K - reasonable ? */
36 static unsigned DefaultICalStringCapacity = 1024;
38 + (id)sharedICalendarRenderer {
40 renderer = [[self alloc] init];
46 - (void)addPreambleForAppointment:(iCalEvent *)_apt
47 toString:(NSMutableString *)s
49 [s appendString:@"BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\n"];
50 [s appendFormat:@"PRODID:NGiCal/%i.%i\r\n",
51 SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION];
52 [s appendString:@"VERSION:2.0\r\n"];
54 - (void)addPostambleForAppointment:(iCalEvent *)_apt
55 toString:(NSMutableString *)s
57 [s appendString:@"END:VCALENDAR\r\n"];
60 - (void)addOrganizer:(iCalPerson *)p toString:(NSMutableString *)s {
63 if (![p isNotNull]) return;
65 [s appendString:@"ORGANIZER;CN=\""];
67 [s appendString:[x iCalDQUOTESafeString]];
69 [s appendString:@"\""];
70 if ((x = [p email])) {
71 [s appendString:@":"]; /* sic! */
72 [s appendString:[x iCalSafeString]];
74 [s appendString:@"\r\n"];
77 - (void)addAttendees:(NSArray *)persons toString:(NSMutableString *)s {
81 count = [persons count];
82 for (i = 0; i < count; i++) {
85 p = [persons objectAtIndex:i];
86 [s appendString:@"ATTENDEE;"];
89 [s appendString:@"ROLE="];
90 [s appendString:[x iCalSafeString]];
91 [s appendString:@";"];
94 [s appendString:@"CN=\""];
96 [s appendString:[x iCalDQUOTESafeString]];
98 [s appendString:@"\""];
99 if ([(x = [p email]) isNotNull]) {
100 [s appendString:@":"]; /* sic! */
101 [s appendString:[x iCalSafeString]];
103 [s appendString:@"\r\n"];
107 - (void)addVEventForAppointment:(iCalEvent *)event
108 toString:(NSMutableString *)s
112 [s appendString:@"BEGIN:VEVENT\r\n"];
114 [s appendString:@"SUMMARY:"];
115 [s appendString:[[event summary] iCalSafeString]];
116 [s appendString:@"\r\n"];
117 if ([[event location] length] > 0) {
118 [s appendString:@"LOCATION:"];
119 [s appendString:[[event location] iCalSafeString]];
120 [s appendString:@"\r\n"];
123 if ((tmp = [event uid]) != nil) {
124 [s appendString:@"UID:"];
125 [s appendString:tmp];
126 [s appendString:@"\r\n"];
129 [s appendString:@"DTSTART:"];
130 [s appendString:[[event startDate] icalString]];
131 [s appendString:@"\r\n"];
133 if ([event hasEndDate]) {
134 [s appendString:@"DTEND:"];
135 [s appendString:[[event endDate] icalString]];
136 [s appendString:@"\r\n"];
138 if ([event hasDuration]) {
139 [s appendString:@"DURATION:"];
140 [s appendString:[event duration]];
141 [s appendString:@"\r\n"];
143 if ([[event priority] length] > 0) {
144 [s appendString:@"PRIORITY:"];
145 [s appendString:[event priority]];
146 [s appendString:@"\r\n"];
148 if ([[event categories] length] > 0) {
151 catString = [event categories];
152 [s appendString:@"CATEGORIES:"];
153 [s appendString:catString];
154 [s appendString:@"\r\n"];
156 if ([[event comment] length] > 0) {
157 [s appendString:@"DESCRIPTION:"]; /* this is what iCal.app does */
158 [s appendString:[[event comment] iCalSafeString]];
159 [s appendString:@"\r\n"];
162 if ((tmp = [event status]) != nil) {
163 [s appendString:@"STATUS:"];
164 [s appendString:tmp];
165 [s appendString:@"\r\n"];
169 /* what's all this? */
170 [s appendString:@"TRANSP:OPAQUE\r\n"]; /* transparency */
171 [s appendString:@"CLASS:PRIVATE\r\n"]; /* classification [like 'top secret'] */
173 [self addOrganizer:[event organizer] toString:s];
174 [self addAttendees:[event attendees] toString:s];
177 [s appendString:@"END:VEVENT\r\n"];
180 - (BOOL)isValidAppointment:(iCalEvent *)_apt {
181 if (![_apt isNotNull])
184 if ([[_apt uid] length] == 0) {
186 @"WARNING: got apt without uid, rejecting iCal generation: %@",
190 if ([[[_apt startDate] icalString] length] == 0) {
192 @"WARNING: got apt without start date, "
193 @"rejecting iCal generation: %@",
201 - (NSString *)vEventStringForEvent:(iCalEvent *)_apt {
204 if (![self isValidAppointment:_apt])
207 s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
208 [self addVEventForAppointment:_apt toString:s];
212 - (NSString *)iCalendarStringForEvent:(iCalEvent *)_apt {
215 if (![self isValidAppointment:_apt])
218 s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
219 [self addPreambleForAppointment:_apt toString:s];
220 [self addVEventForAppointment:_apt toString:s];
221 [self addPostambleForAppointment:_apt toString:s];
225 @end /* iCalRenderer */
227 @interface NSString (SOGoiCal_Private)
228 - (NSString *)iCalCleanString;
231 @interface SOGoICalStringEscaper : NSObject <NGStringEscaping>
237 @implementation SOGoICalStringEscaper
238 + (id)sharedEscaper {
239 static id sharedInstance = nil;
240 if (!sharedInstance) {
241 sharedInstance = [[self alloc] init];
243 return sharedInstance;
246 - (NSString *)stringByEscapingString:(NSString *)_s {
249 if (!_s || [_s length] == 0)
252 c = [_s characterAtIndex:0];
256 else if (c == '\r') {
257 return nil; /* effectively remove char */
259 return [NSString stringWithFormat:@"\\%@", _s];
264 @implementation NSString (SOGoiCal)
267 - (NSString *)iCalFoldedString {
268 /* RFC2445, 4.1 Content Lines
270 The iCalendar object is organized into individual lines of text,
271 called content lines. Content lines are delimited by a line break,
272 which is a CRLF sequence (US-ASCII decimal 13, followed by US-ASCII
274 Lines of text SHOULD NOT be longer than 75 octets, excluding the line
275 break. Long content lines SHOULD be split into a multiple line
276 representations using a line "folding" technique. That is, a long
277 line can be split between any two characters by inserting a CRLF
278 immediately followed by a single linear white space character (i.e.,
279 SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9).
280 Any sequence of CRLF followed immediately by a single linear white space
281 character is ignored (i.e., removed) when processing the content type.
286 /* strip off any characters from string which are not allowed in iCal */
287 - (NSString *)iCalCleanString {
288 static NSCharacterSet *replaceSet = nil;
290 if (replaceSet == nil) {
291 replaceSet = [NSCharacterSet characterSetWithCharactersInString:@"\r"];
295 return [self stringByEscapingCharactersFromSet:replaceSet
296 usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
299 - (NSString *)iCalDQUOTESafeString {
300 static NSCharacterSet *escapeSet = nil;
302 if (escapeSet == nil) {
303 escapeSet = [NSCharacterSet characterSetWithCharactersInString:@"\\\""];
306 return [self iCalEscapedStringWithEscapeSet:escapeSet];
309 - (NSString *)iCalSafeString {
310 static NSCharacterSet *escapeSet = nil;
312 if (escapeSet == nil) {
314 [[NSCharacterSet characterSetWithCharactersInString:@"\n,;\\\""] retain];
316 return [self iCalEscapedStringWithEscapeSet:escapeSet];
319 /* Escape unsafe characters */
320 - (NSString *)iCalEscapedStringWithEscapeSet:(NSCharacterSet *)_es {
323 s = [self iCalCleanString];
324 return [s stringByEscapingCharactersFromSet:_es
325 usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
328 @end /* NSString (SOGoiCal) */