]> err.no Git - sope/blob - sope-ical/NGiCal/iCalRenderer.m
fixed some OSX issue
[sope] / sope-ical / NGiCal / iCalRenderer.m
1 /*
2   Copyright (C) 2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo 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
9   later version.
10
11   OGo 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.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "iCalRenderer.h"
23 #include "iCalEvent.h"
24 #include "iCalPerson.h"
25 #include "common.h"
26
27 @interface NSDate(UsedPrivates)
28 - (NSString *)icalString; // declared in NGiCal
29 @end
30
31 @implementation iCalRenderer
32
33 static iCalRenderer *renderer = nil;
34
35 /* assume length of 1K - reasonable ? */
36 static unsigned DefaultICalStringCapacity = 1024;
37
38 + (id)sharedICalendarRenderer {
39   if (renderer == nil)
40     renderer = [[self alloc] init];
41   return renderer;
42 }
43
44 /* renderer */
45
46 - (void)addPreambleForAppointment:(iCalEvent *)_apt
47   toString:(NSMutableString *)s
48 {
49   [s appendString:@"BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\n"];
50   [s appendString:@"PRODID:NGiCal/4.3\r\n"];
51   [s appendString:@"VERSION:2.0\r\n"];
52 }
53 - (void)addPostambleForAppointment:(iCalEvent *)_apt
54   toString:(NSMutableString *)s
55 {
56   [s appendString:@"END:VCALENDAR\r\n"];
57 }
58
59 - (void)addOrganizer:(iCalPerson *)p toString:(NSMutableString *)s {
60   NSString *x;
61   
62   if (![p isNotNull]) return;
63   
64   [s appendString:@"ORGANIZER;CN=\""];
65   if ((x = [p cn]))
66     [s appendString:[x iCalDQUOTESafeString]];
67   
68   [s appendString:@"\""];
69   if ((x = [p email])) {
70     [s appendString:@":"]; /* sic! */
71     [s appendString:[x iCalSafeString]];
72   }
73   [s appendString:@"\r\n"];
74 }
75
76 - (void)addAttendees:(NSArray *)persons toString:(NSMutableString *)s {
77   unsigned   i, count;
78   iCalPerson *p;
79
80   count   = [persons count];
81   for (i = 0; i < count; i++) {
82     NSString *x;
83     
84     p = [persons objectAtIndex:i];
85     [s appendString:@"ATTENDEE;"];
86     
87     if ((x = [p role])) {
88       [s appendString:@"ROLE="];
89       [s appendString:[x iCalSafeString]];
90       [s appendString:@";"];
91     }
92     
93     [s appendString:@"CN=\""];
94     if ((x = [p cn])) {
95       [s appendString:[x iCalDQUOTESafeString]];
96     }
97     [s appendString:@"\""];
98     if ([(x = [p email]) isNotNull]) {
99       [s appendString:@":"]; /* sic! */
100       [s appendString:[x iCalSafeString]];
101     }
102     [s appendString:@"\r\n"];
103   }
104 }
105
106 - (void)addVEventForAppointment:(iCalEvent *)event
107   toString:(NSMutableString *)s
108 {
109   id tmp;
110   
111   [s appendString:@"BEGIN:VEVENT\r\n"];
112   
113   [s appendString:@"SUMMARY:"];
114   [s appendString:[[event summary] iCalSafeString]];
115   [s appendString:@"\r\n"];
116   if ([[event location] length] > 0) {
117     [s appendString:@"LOCATION:"];
118     [s appendString:[[event location] iCalSafeString]];
119     [s appendString:@"\r\n"];
120   }
121   
122   if ((tmp = [event uid]) != nil) {
123     [s appendString:@"UID:"];
124     [s appendString:tmp];
125     [s appendString:@"\r\n"];
126   }
127   
128   [s appendString:@"DTSTART:"];
129   [s appendString:[[event startDate] icalString]];
130   [s appendString:@"\r\n"];
131   
132   if ([event hasEndDate]) {
133     [s appendString:@"DTEND:"];
134     [s appendString:[[event endDate] icalString]];
135     [s appendString:@"\r\n"];
136   }
137   if ([event hasDuration]) {
138     [s appendString:@"DURATION:"];
139     [s appendString:[event duration]];
140     [s appendString:@"\r\n"];
141   }
142   if ([[event priority] length] > 0) {
143     [s appendString:@"PRIORITY:"];
144     [s appendString:[event priority]];
145     [s appendString:@"\r\n"];
146   }
147   if ([[event categories] length] > 0) {
148     NSString *catString;
149     
150     catString = [event categories];
151     [s appendString:@"CATEGORIES:"];
152     [s appendString:catString];
153     [s appendString:@"\r\n"];
154   }
155   if ([[event comment] length] > 0) {
156     [s appendString:@"DESCRIPTION:"]; /* this is what iCal.app does */
157     [s appendString:[[event comment] iCalSafeString]];
158     [s appendString:@"\r\n"];
159   }
160
161   if ((tmp = [event status]) != nil) {
162     [s appendString:@"STATUS:"];
163     [s appendString:tmp];
164     [s appendString:@"\r\n"];
165   }
166
167   
168   /* what's all this? */
169   [s appendString:@"TRANSP:OPAQUE\r\n"]; /* transparency */
170   [s appendString:@"CLASS:PRIVATE\r\n"]; /* classification [like 'top secret'] */
171   
172   [self addOrganizer:[event organizer] toString:s];
173   [self addAttendees:[event attendees] toString:s];
174   
175   /* postamble */
176   [s appendString:@"END:VEVENT\r\n"];
177 }
178
179 - (BOOL)isValidAppointment:(iCalEvent *)_apt {
180   if (![_apt isNotNull])
181     return NO;
182   
183   if ([[_apt uid] length] == 0) {
184     [self logWithFormat:
185             @"WARNING: got apt without uid, rejecting iCal generation: %@", 
186             _apt];
187     return NO;
188   }
189   if ([[[_apt startDate] icalString] length] == 0) {
190     [self logWithFormat:
191             @"WARNING: got apt without start date, "
192             @"rejecting iCal generation: %@",
193             _apt];
194     return NO;
195   }
196   
197   return YES;
198 }
199
200 - (NSString *)vEventStringForEvent:(iCalEvent *)_apt {
201   NSMutableString *s;
202   
203   if (![self isValidAppointment:_apt])
204     return nil;
205   
206   s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
207   [self addVEventForAppointment:_apt toString:s];
208   return s;
209 }
210
211 - (NSString *)iCalendarStringForEvent:(iCalEvent *)_apt {
212   NSMutableString *s;
213   
214   if (![self isValidAppointment:_apt])
215     return nil;
216   
217   s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
218   [self addPreambleForAppointment:_apt  toString:s];
219   [self addVEventForAppointment:_apt    toString:s];
220   [self addPostambleForAppointment:_apt toString:s];
221   return s;
222 }
223
224 @end /* iCalRenderer */
225
226 @interface NSString (SOGoiCal_Private)
227 - (NSString *)iCalCleanString;
228 @end
229
230 @interface SOGoICalStringEscaper : NSObject <NGStringEscaping>
231 {
232 }
233 + (id)sharedEscaper;
234 @end
235
236 @implementation SOGoICalStringEscaper
237 + (id)sharedEscaper {
238   static id sharedInstance = nil;
239   if (!sharedInstance) {
240     sharedInstance = [[self alloc] init];
241   }
242   return sharedInstance;
243 }
244
245 - (NSString *)stringByEscapingString:(NSString *)_s {
246   unichar c;
247
248   if (!_s || [_s length] == 0)
249     return nil;
250
251   c = [_s characterAtIndex:0];
252   if (c == '\n') {
253     return @"\\n";
254   }
255   else if (c == '\r') {
256     return nil; /* effectively remove char */
257   }
258   return [NSString stringWithFormat:@"\\%@", _s];
259 }
260
261 @end
262
263 @implementation NSString (SOGoiCal)
264
265 #if 0
266 - (NSString *)iCalFoldedString {
267   /* RFC2445, 4.1 Content Lines
268   
269   The iCalendar object is organized into individual lines of text,
270   called content lines. Content lines are delimited by a line break,
271   which is a CRLF sequence (US-ASCII decimal 13, followed by US-ASCII
272                             decimal 10).
273   Lines of text SHOULD NOT be longer than 75 octets, excluding the line
274   break. Long content lines SHOULD be split into a multiple line
275   representations using a line "folding" technique. That is, a long
276   line can be split between any two characters by inserting a CRLF
277   immediately followed by a single linear white space character (i.e.,
278   SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9).
279   Any sequence of CRLF followed immediately by a single linear white space
280   character is ignored (i.e., removed) when processing the content type.
281   */
282 }
283 #endif
284
285 /* strip off any characters from string which are not allowed in iCal */
286 - (NSString *)iCalCleanString {
287   static NSCharacterSet *replaceSet = nil;
288
289   if (replaceSet == nil) {
290     replaceSet = [NSCharacterSet characterSetWithCharactersInString:@"\r"];
291     [replaceSet retain];
292   }
293   
294   return [self stringByEscapingCharactersFromSet:replaceSet
295                usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
296 }
297
298 - (NSString *)iCalDQUOTESafeString {
299   static NSCharacterSet *escapeSet = nil;
300   
301   if (escapeSet == nil) {
302     escapeSet = [NSCharacterSet characterSetWithCharactersInString:@"\\\""];
303     [escapeSet retain];
304   }
305   return [self iCalEscapedStringWithEscapeSet:escapeSet];
306 }
307
308 - (NSString *)iCalSafeString {
309   static NSCharacterSet *escapeSet = nil;
310   
311   if (escapeSet == nil) {
312     escapeSet = 
313       [[NSCharacterSet characterSetWithCharactersInString:@"\n,;\\\""] retain];
314   }
315   return [self iCalEscapedStringWithEscapeSet:escapeSet];
316 }
317
318 /* Escape unsafe characters */
319 - (NSString *)iCalEscapedStringWithEscapeSet:(NSCharacterSet *)_es {
320   NSString *s;
321
322   s = [self iCalCleanString];
323   return [s stringByEscapingCharactersFromSet:_es
324             usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
325 }
326
327 @end /* NSString (SOGoiCal) */