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