2 Copyright (C) 2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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 appendString:@"PRODID:NGiCal/4.3\r\n"];
51 [s appendString:@"VERSION:2.0\r\n"];
53 - (void)addPostambleForAppointment:(iCalEvent *)_apt
54 toString:(NSMutableString *)s
56 [s appendString:@"END:VCALENDAR\r\n"];
59 - (void)addOrganizer:(iCalPerson *)p toString:(NSMutableString *)s {
62 if (![p isNotNull]) return;
64 [s appendString:@"ORGANIZER;CN=\""];
66 [s appendString:[x iCalDQUOTESafeString]];
68 [s appendString:@"\""];
69 if ((x = [p email])) {
70 [s appendString:@":"]; /* sic! */
71 [s appendString:[x iCalSafeString]];
73 [s appendString:@"\r\n"];
76 - (void)addAttendees:(NSArray *)persons toString:(NSMutableString *)s {
80 count = [persons count];
81 for (i = 0; i < count; i++) {
84 p = [persons objectAtIndex:i];
85 [s appendString:@"ATTENDEE;"];
88 [s appendString:@"ROLE="];
89 [s appendString:[x iCalSafeString]];
90 [s appendString:@";"];
93 [s appendString:@"CN=\""];
95 [s appendString:[x iCalDQUOTESafeString]];
97 [s appendString:@"\""];
98 if ([(x = [p email]) isNotNull]) {
99 [s appendString:@":"]; /* sic! */
100 [s appendString:[x iCalSafeString]];
102 [s appendString:@"\r\n"];
106 - (void)addVEventForAppointment:(iCalEvent *)event
107 toString:(NSMutableString *)s
111 [s appendString:@"BEGIN:VEVENT\r\n"];
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"];
122 if ((tmp = [event uid]) != nil) {
123 [s appendString:@"UID:"];
124 [s appendString:tmp];
125 [s appendString:@"\r\n"];
128 [s appendString:@"DTSTART:"];
129 [s appendString:[[event startDate] icalString]];
130 [s appendString:@"\r\n"];
132 if ([event hasEndDate]) {
133 [s appendString:@"DTEND:"];
134 [s appendString:[[event endDate] icalString]];
135 [s appendString:@"\r\n"];
137 if ([event hasDuration]) {
138 [s appendString:@"DURATION:"];
139 [s appendString:[event duration]];
140 [s appendString:@"\r\n"];
142 if ([[event priority] length] > 0) {
143 [s appendString:@"PRIORITY:"];
144 [s appendString:[event priority]];
145 [s appendString:@"\r\n"];
147 if ([[event categories] length] > 0) {
150 catString = [event categories];
151 [s appendString:@"CATEGORIES:"];
152 [s appendString:catString];
153 [s appendString:@"\r\n"];
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"];
161 if ((tmp = [event status]) != nil) {
162 [s appendString:@"STATUS:"];
163 [s appendString:tmp];
164 [s appendString:@"\r\n"];
168 /* what's all this? */
169 [s appendString:@"TRANSP:OPAQUE\r\n"]; /* transparency */
170 [s appendString:@"CLASS:PRIVATE\r\n"]; /* classification [like 'top secret'] */
172 [self addOrganizer:[event organizer] toString:s];
173 [self addAttendees:[event attendees] toString:s];
176 [s appendString:@"END:VEVENT\r\n"];
179 - (BOOL)isValidAppointment:(iCalEvent *)_apt {
180 if (![_apt isNotNull])
183 if ([[_apt uid] length] == 0) {
185 @"WARNING: got apt without uid, rejecting iCal generation: %@",
189 if ([[[_apt startDate] icalString] length] == 0) {
191 @"WARNING: got apt without start date, "
192 @"rejecting iCal generation: %@",
200 - (NSString *)vEventStringForEvent:(iCalEvent *)_apt {
203 if (![self isValidAppointment:_apt])
206 s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
207 [self addVEventForAppointment:_apt toString:s];
211 - (NSString *)iCalendarStringForEvent:(iCalEvent *)_apt {
214 if (![self isValidAppointment:_apt])
217 s = [NSMutableString stringWithCapacity:DefaultICalStringCapacity];
218 [self addPreambleForAppointment:_apt toString:s];
219 [self addVEventForAppointment:_apt toString:s];
220 [self addPostambleForAppointment:_apt toString:s];
224 @end /* iCalRenderer */
226 @interface NSString (SOGoiCal_Private)
227 - (NSString *)iCalCleanString;
230 @interface SOGoICalStringEscaper : NSObject <NGStringEscaping>
236 @implementation SOGoICalStringEscaper
237 + (id)sharedEscaper {
238 static id sharedInstance = nil;
239 if (!sharedInstance) {
240 sharedInstance = [[self alloc] init];
242 return sharedInstance;
245 - (NSString *)stringByEscapingString:(NSString *)_s {
248 if (!_s || [_s length] == 0)
251 c = [_s characterAtIndex:0];
255 else if (c == '\r') {
256 return nil; /* effectively remove char */
258 return [NSString stringWithFormat:@"\\%@", _s];
263 @implementation NSString (SOGoiCal)
266 - (NSString *)iCalFoldedString {
267 /* RFC2445, 4.1 Content Lines
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
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.
285 /* strip off any characters from string which are not allowed in iCal */
286 - (NSString *)iCalCleanString {
287 static NSCharacterSet *replaceSet = nil;
289 if (replaceSet == nil) {
290 replaceSet = [NSCharacterSet characterSetWithCharactersInString:@"\r"];
294 return [self stringByEscapingCharactersFromSet:replaceSet
295 usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
298 - (NSString *)iCalDQUOTESafeString {
299 static NSCharacterSet *escapeSet = nil;
301 if (escapeSet == nil) {
302 escapeSet = [NSCharacterSet characterSetWithCharactersInString:@"\\\""];
305 return [self iCalEscapedStringWithEscapeSet:escapeSet];
308 - (NSString *)iCalSafeString {
309 static NSCharacterSet *escapeSet = nil;
311 if (escapeSet == nil) {
313 [[NSCharacterSet characterSetWithCharactersInString:@"\n,;\\\""] retain];
315 return [self iCalEscapedStringWithEscapeSet:escapeSet];
318 /* Escape unsafe characters */
319 - (NSString *)iCalEscapedStringWithEscapeSet:(NSCharacterSet *)_es {
322 s = [self iCalCleanString];
323 return [s stringByEscapingCharactersFromSet:_es
324 usingStringEscaping:[SOGoICalStringEscaper sharedEscaper]];
327 @end /* NSString (SOGoiCal) */