1/* iCalXMLRenderer.m - this file is part of SOPE 2 * 3 * Copyright (C) 2006 Inverse inc. 4 * 5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> 6 * 7 * This file is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2, or (at your option) 10 * any later version. 11 * 12 * This file is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; see the file COPYING. If not, write to 19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 20 * Boston, MA 02111-1307, USA. 21 */ 22 23/* This class implements most of the XML iCalendar spec as defined here: 24 http://tools.ietf.org/html/rfc6321 */ 25 26#import <Foundation/NSArray.h> 27#import <Foundation/NSDictionary.h> 28 29#import <NGExtensions/NSObject+Logs.h> 30#import <NGExtensions/NSString+misc.h> 31 32#import "iCalCalendar.h" 33#import "iCalDateTime.h" 34#import "iCalPerson.h" 35#import "iCalUTCOffset.h" 36 37#import "iCalXMLRenderer.h" 38 39@interface CardElement (iCalXMLExtension) 40 41- (NSString *) xmlRender; 42 43@end 44 45@interface iCalXMLRenderer (PrivateAPI) 46 47- (NSString *) renderElement: (CardElement *) anElement; 48- (NSString *) renderGroup: (CardGroup *) aGroup; 49 50@end 51 52@implementation iCalXMLRenderer 53 54+ (iCalXMLRenderer *) sharedXMLRenderer 55{ 56 static iCalXMLRenderer *sharedXMLRenderer = nil; 57 58 if (!sharedXMLRenderer) 59 sharedXMLRenderer = [self new]; 60 61 return sharedXMLRenderer; 62} 63 64- (NSString *) render: (iCalCalendar *) calendar 65{ 66 return [NSString stringWithFormat: 67 @"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 68 @"<icalendar xmlns=\"urn:ietf:params:xml:ns:icalendar-2.0\">" 69 @"%@" 70 @"</icalendar>", 71 [calendar xmlRender]]; 72} 73 74@end 75 76@implementation CardElement (iCalXMLExtension) 77 78- (NSString *) xmlValueTag 79{ 80 return @"text"; 81} 82 83- (NSString *) xmlParameterTag: (NSString *) paramName 84{ 85 return nil; 86} 87 88- (void) _appendPaddingValues: (int) max 89 withTag: (NSString *) valueTag 90 intoString: (NSMutableString *) rendering 91{ 92 int count; 93 94 for (count = 0; count < max; count++) 95 [rendering appendFormat: @"<%@/>", valueTag]; 96} 97 98- (NSString *) _xmlRenderParameter: (NSString *) paramName 99{ 100 NSMutableString *rendering; 101 NSArray *paramValues; 102 NSString *lowerName, *paramTypeTag, *escapedValue; 103 int count, max; 104 105 paramValues = [attributes objectForKey: paramName]; 106 max = [paramValues count]; 107 if (max > 0) 108 { 109 lowerName = [paramName lowercaseString]; 110 rendering = [NSMutableString stringWithCapacity: 32]; 111 paramTypeTag = [self xmlParameterTag: [paramName lowercaseString]]; 112 for (count = 0; count < max; count++) 113 { 114 [rendering appendFormat: @"<%@>", lowerName]; 115 if (paramTypeTag) 116 [rendering appendFormat: @"<%@>", paramTypeTag]; 117 escapedValue = [[paramValues objectAtIndex: count] 118 stringByEscapingXMLString]; 119 [rendering appendFormat: @"%@", escapedValue]; 120 if (paramTypeTag) 121 [rendering appendFormat: @"</%@>", paramTypeTag]; 122 [rendering appendFormat: @"</%@>", lowerName]; 123 } 124 } 125 else 126 rendering = nil; 127 128 return rendering; 129} 130 131- (NSString *) _xmlRenderParameters 132{ 133 NSArray *keys; 134 NSMutableString *rendering; 135 NSString *currentValue; 136 int count, max; 137 138 keys = [attributes allKeys]; 139 max = [keys count]; 140 if (max > 0) 141 { 142 rendering = [NSMutableString stringWithCapacity: 64]; 143 for (count = 0; count < max; count++) 144 { 145 currentValue 146 = [self _xmlRenderParameter: [keys objectAtIndex: count]]; 147 if ([currentValue length] > 0) 148 [rendering appendString: currentValue]; 149 } 150 } 151 else 152 rendering = nil; 153 154 return rendering; 155} 156 157- (NSString *) _xmlRenderValue 158{ 159 NSMutableString *rendering; 160 NSArray *keys, *orderedValues, *subValues; 161 NSString *key, *valueTag; 162 NSUInteger count, max, oCount, oMax, sCount, sMax; 163 164#warning this code should be fix to comply better with the RFC 165 rendering = [NSMutableString stringWithCapacity: 64]; 166 167 valueTag = [self xmlValueTag]; 168 169 keys = [values allKeys]; 170 max = [keys count]; 171 for (count = 0; count < max; count++) 172 { 173 key = [keys objectAtIndex: count]; 174 orderedValues = [values objectForKey: key]; 175 oMax = [orderedValues count]; 176 for (oCount = 0; oCount < oMax; oCount++) 177 { 178 if ([key length] > 0) 179 [rendering appendFormat: @"<%@>", [key lowercaseString]]; 180 else 181 [rendering appendFormat: @"<%@>", valueTag]; 182 183 subValues = [orderedValues objectAtIndex: oCount]; 184 sMax = [subValues count]; 185 for (sCount = 0; sCount < sMax; sCount++) 186 [rendering appendString: [[subValues objectAtIndex: sCount] stringByEscapingXMLString]]; 187 188 if ([key length] > 0) 189 [rendering appendFormat: @"</%@>", [key lowercaseString]]; 190 else 191 [rendering appendFormat: @"</%@>", valueTag]; 192 } 193 } 194 195 return rendering; 196} 197 198- (NSString *) xmlRender 199{ 200 NSMutableString *rendering; 201 NSString *lowerTag, *rParameters, *value; 202 203 rParameters = [self _xmlRenderParameters]; 204 value = [self _xmlRenderValue]; 205 if ([value length]) 206 { 207 rendering = [NSMutableString stringWithCapacity: 128]; 208 lowerTag = [tag lowercaseString]; 209 [rendering appendFormat: @"<%@>", lowerTag]; 210 if ([rParameters length] > 0) 211 [rendering appendFormat: @"<parameters>%@</parameters>", 212 rParameters]; 213 [rendering appendString: value]; 214 [rendering appendFormat: @"</%@>", lowerTag]; 215 } 216 else 217 rendering = nil; 218 219 return rendering; 220} 221 222@end 223 224@implementation iCalDateTime (iCalXMLExtension) 225 226- (NSString *) xmlValueTag 227{ 228 return ([self isAllDay] ? @"date" : @"date-time"); 229} 230 231@end 232 233@implementation iCalPerson (iCalXMLExtension) 234 235- (NSString *) xmlParameterTag: (NSString *) paramName 236{ 237 NSString *paramTag; 238 239 if ([paramName isEqualToString: @"delegated-from"] 240 || [paramName isEqualToString: @"delegated-to"] 241 || [paramName isEqualToString: @"sent-by"]) 242 paramTag = @"cal-address"; 243 else 244 paramTag = [super xmlParameterTag: paramName]; 245 246 return paramTag; 247} 248 249- (NSString *) xmlValueTag 250{ 251 return @"cal-address"; 252} 253 254@end 255 256// @implementation iCalRecurrenceRule (iCalXMLExtension) 257 258// - (NSString *) _xmlRenderValue 259// { 260// NSMutableString *rendering; 261// NSArray *valueParts; 262// NSString *valueTag, *currentValue; 263// int count, max; 264 265// max = [values count]; 266// rendering = [NSMutableString stringWithCapacity: 64]; 267// for (count = 0; count < max; count++) 268// { 269// currentValue = [[values objectAtIndex: count] 270// stringByEscapingXMLString]; 271// if ([currentValue length] > 0) 272// { 273// valueParts = [currentValue componentsSeparatedByString: @"="]; 274// if ([valueParts count] == 2) 275// { 276// valueTag = [[valueParts objectAtIndex: 0] lowercaseString]; 277// [rendering appendFormat: @"<%@>%@</%@>", 278// valueTag, 279// [valueParts objectAtIndex: 1], 280// valueTag]; 281// } 282// } 283// } 284 285// return rendering; 286// } 287 288// @end 289 290@implementation iCalUTCOffset (iCalXMLExtension) 291 292- (NSString *) xmlValueTag 293{ 294 return @"utc-offset"; 295} 296 297@end 298 299@implementation CardGroup (iCalXMLExtension) 300 301- (NSString *) xmlRender 302{ 303 NSString *lowerTag, *childRendering; 304 int count, max; 305 NSMutableString *rendering; 306 NSMutableArray *properties, *components; 307 CardElement *currentChild; 308 309 rendering = [NSMutableString stringWithCapacity: 4096]; 310 max = [children count]; 311 if (max > 0) 312 { 313 properties = [[NSMutableArray alloc] initWithCapacity: max]; 314 components = [[NSMutableArray alloc] initWithCapacity: max]; 315 for (count = 0; count < max; count++) 316 { 317 currentChild = [children objectAtIndex: count]; 318 childRendering = [currentChild xmlRender]; 319 if (childRendering) 320 { 321 if ([currentChild isKindOfClass: [CardGroup class]]) 322 [components addObject: childRendering]; 323 else 324 [properties addObject: childRendering]; 325 } 326 } 327 328 lowerTag = [tag lowercaseString]; 329 [rendering appendFormat: @"<%@>", lowerTag]; 330 if ([properties count] > 0) 331 [rendering appendFormat: @"<properties>%@</properties>", 332 [properties componentsJoinedByString: @""]]; 333 if ([components count] > 0) 334 [rendering appendFormat: @"<properties>%@</properties>", 335 [components componentsJoinedByString: @""]]; 336 [rendering appendFormat: @"</%@>", lowerTag]; 337 } 338 339 return rendering; 340} 341 342@end 343 344// - (NSString *) renderElement: (CardElement *) anElement 345// { 346// NSMutableString *rendering; 347// NSDictionary *attributes; 348// NSEnumerator *keys; 349// NSArray *values, *renderedAttrs; 350// NSString *key, *finalRendering, *tag; 351 352// if (![anElement isVoid]) 353// { 354// rendering = [NSMutableString string]; 355// if ([anElement group]) 356// [rendering appendFormat: @"%@.", [anElement group]]; 357// tag = [anElement tag]; 358// if (!(tag && [tag length])) 359// { 360// tag = @"<no-tag>"; 361// [self warnWithFormat: @"card element of class '%@' has an empty tag", 362// NSStringFromClass([anElement class])]; 363// } 364 365// [rendering appendString: [tag uppercaseString]]; 366// attributes = [anElement attributes]; 367// keys = [[attributes allKeys] objectEnumerator]; 368// while ((key = [keys nextObject])) 369// { 370// NSString *s; 371// int i, c; 372 373// renderedAttrs = [[attributes objectForKey: key] renderedForCards]; 374// c = [renderedAttrs count]; 375// if (c > 0) 376// { 377// [rendering appendFormat: @";%@=", [key uppercaseString]]; 378 379// for (i = 0; i < c; i++) 380// { 381// s = [renderedAttrs objectAtIndex: i]; 382 383// /* We MUST quote attribute values that have a ":" in them 384// and that not already quoted */ 385// if ([s length] > 2 && [s rangeOfString: @":"].length && 386// [s characterAtIndex: 0] != '"' && ![s hasSuffix: @"\""]) 387// s = [NSString stringWithFormat: @"\"%@\"", s]; 388 389// [rendering appendFormat: @"%@", s]; 390 391// if (i+1 < c) 392// [rendering appendString: @","]; 393// } 394// } 395// } 396 397// values = [anElement values]; 398// if ([values count] > 0) 399// [rendering appendFormat: @":%@", 400// [[values renderedForCards] componentsJoinedByString: @";"]]; 401 402// if ([rendering length] > 0) 403// [rendering appendString: @"\r\n"]; 404 405// finalRendering = [rendering foldedForVersitCards]; 406// } 407// else 408// finalRendering = @""; 409 410// return finalRendering; 411// } 412 413// - (NSString *) renderGroup: (CardGroup *) aGroup 414// { 415// NSEnumerator *children; 416// CardElement *currentChild; 417// NSMutableString *rendering; 418// NSString *groupTag; 419 420// rendering = [NSMutableString string]; 421 422// groupTag = [aGroup tag]; 423// if (!(groupTag && [groupTag length])) 424// { 425// groupTag = @"<no-tag>"; 426// [self warnWithFormat: @"card group of class '%@' has an empty tag", 427// NSStringFromClass([aGroup class])]; 428// } 429 430// groupTag = [groupTag uppercaseString]; 431// [rendering appendFormat: @"BEGIN:%@\r\n", groupTag]; 432// children = [[aGroup children] objectEnumerator]; 433// currentChild = [children nextObject]; 434// while (currentChild) 435// { 436// [rendering appendString: [self render: currentChild]]; 437// currentChild = [children nextObject]; 438// } 439// [rendering appendFormat: @"END:%@\r\n", groupTag]; 440 441// return rendering; 442// } 443 444// @end 445