]> err.no Git - sope/blob - sope-ical/iCalSaxDriver/ICalSaxParser.m
minor tweaks
[sope] / sope-ical / iCalSaxDriver / ICalSaxParser.m
1 /*
2   Copyright (C) 2000-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 "ICalSaxParser.h"
23 #include "NSString+ICal.h"
24 #include <SaxObjC/XMLNamespaces.h>
25 #include <SaxObjC/SaxAttributes.h>
26 #include "common.h"
27 #include <ical.h>
28
29 /*
30   OPEN: apply default attributes in conformance to xcal !
31 */
32
33 @interface ICalSaxParser(Privates)
34
35 /* walking */
36
37 - (void)logUnknownComponentKind:(icalcomponent *)_comp;
38 - (void)walkSubComponents:(icalcomponent *)_comp;
39 - (void)walkComponent:(icalcomponent *)_comp;
40
41 @end
42
43 static BOOL debugOn = NO;
44
45 static int _UTF8ToUTF16(unsigned char **sourceStart, unsigned char *sourceEnd, 
46                         unichar **targetStart, const unichar *targetEnd);
47
48 @implementation ICalSaxParser 
49
50 static Class StrClass = Nil;
51
52 + (void)initialize {
53   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
54   StrClass = [NSString class];
55
56   debugOn = [ud boolForKey:@"iCalSaxDriverDebugEnabled"];
57 }
58
59 - (void)dealloc {
60   [self->contentHandler release];
61   [self->errorHandler   release];
62   [self->attrs          release];
63   [super dealloc];
64 }
65
66 /* logging of unknown elements etc */
67
68 - (void)logUnknownComponentKind:(icalcomponent *)_comp {
69   NSLog(@"%s: cannot process component kind: '%s'",
70         __PRETTY_FUNCTION__,
71         _comp
72         ?icalenum_component_kind_to_string(icalcomponent_isa(_comp)):"null");
73 }
74
75 - (void)logUnknownPropertyKind:(icalproperty *)_p {
76   NSLog(@"%s: cannot process property kind: '%s'",
77         __PRETTY_FUNCTION__,
78         _p ? icalenum_property_kind_to_string(icalproperty_isa(_p)) : "null");
79 }
80
81 - (void)logUnknownParameterKind:(icalparameter *)_pa {
82   NSLog(@"%s: cannot process parameter kind: '%s'",
83         __PRETTY_FUNCTION__,
84         _pa ? icalparameter_kind_to_string(icalparameter_isa(_pa)): "null");
85 }
86
87 /* walking */
88
89 - (NSString *)stringForValue:(icalvalue *)_val 
90   ofProperty:(icalproperty *)_p 
91   inComponent:(icalcomponent *)_comp 
92 {
93   const unsigned char *s;
94   int len;
95
96   if (_val == NULL) return nil;
97   
98   if ((s = icalvalue_as_ical_string(_val)) == NULL) {
99     /* error ? */
100     NSLog(@"%s: couldn't process an iCal value (component=%s,property=%s).",
101           __PRETTY_FUNCTION__,
102           icalenum_component_kind_to_string(icalcomponent_isa(_comp)),
103           icalenum_property_kind_to_string(icalproperty_isa(_p)));
104     return nil;
105   }
106   
107   if ((len = strlen(s)) == 0) 
108     return @"";
109
110   /* SHOULD USE UTF-8 ?? */
111   return [StrClass stringWithCString:s];
112 }
113
114 - (void)walkValue:(icalvalue *)_val 
115   ofProperty:(icalproperty *)_p 
116   inComponent:(icalcomponent *)_comp 
117 {
118   const unsigned char *s;
119   unichar  *data, *ts;
120   unsigned len;
121   
122   if (_val == NULL) return;
123   
124   /* generic handling for value: pass them as SAX characters ... */
125   
126   if ((s = icalvalue_as_ical_string(_val)) == NULL) {
127     /* error ? */
128     NSLog(@"%s: couldn't process an iCal value (component=%s,property=%s).",
129           __PRETTY_FUNCTION__,
130           icalenum_component_kind_to_string(icalcomponent_isa(_comp)),
131           icalenum_property_kind_to_string(icalproperty_isa(_p)));
132     return;
133   }
134   
135   if ((len = strlen(s)) == 0) {
136     unichar c = 0;
137     data = &c;
138     [self->contentHandler characters:data length:0];
139     return;
140   }
141
142   data = ts = calloc(len + 1, sizeof(unichar)); /* GC ?! */
143   
144   if (_UTF8ToUTF16((void *)&s, (void *)(s + len),
145                    (void *)&ts, ts + (len * sizeof(unichar)))) {
146     free(data);
147     NSLog(@"ERROR(%s:%i): couldn't convert UTF8 to UTF16 !",
148           __PRETTY_FUNCTION__, __LINE__);
149   }
150   else {
151     [self->contentHandler characters:data length:((unsigned)(ts - data))];
152     free(data);
153   }
154 }
155
156 - (NSString *)stringForValueParameter:(icalparameter *)_pa {
157   switch (icalparameter_get_value(_pa)) {
158   case ICAL_VALUE_BINARY:     return @"binary";
159   case ICAL_VALUE_BOOLEAN:    return @"boolean";
160   case ICAL_VALUE_DATE:       return @"date";
161   case ICAL_VALUE_DURATION:   return @"duration";
162   case ICAL_VALUE_FLOAT:      return @"float";
163   case ICAL_VALUE_INTEGER:    return @"integer";
164   case ICAL_VALUE_PERIOD:     return @"period";
165   case ICAL_VALUE_RECUR:      return @"recur";
166   case ICAL_VALUE_TEXT:       return @"text";
167   case ICAL_VALUE_TIME:       return @"time";
168   case ICAL_VALUE_URI:        return @"uri";
169   case ICAL_VALUE_ERROR:      return @"error";
170   case ICAL_VALUE_DATETIME:   return @"datetime";
171   case ICAL_VALUE_UTCOFFSET:  return @"utcoffset";
172   case ICAL_VALUE_CALADDRESS: return @"caladdress";
173   default:
174     return nil;
175   }
176 }
177 - (NSString *)stringForTzIdParameter:(icalparameter *)_pa {
178   const char *s;
179
180   if ((s = icalparameter_get_tzid(_pa)))
181     return [StrClass stringWithCString:s];
182   return nil;
183 }
184 - (NSString *)stringForXParameter:(icalparameter *)_pa {
185   const char *s;
186
187   if ((s = icalparameter_get_x(_pa)))
188     return [StrClass stringWithCString:s];
189   return nil;
190 }
191 - (NSString *)stringForCnParameter:(icalparameter *)_pa {
192   const char *s;
193
194   if ((s = icalparameter_get_cn(_pa)))
195     return [StrClass stringWithCString:s];
196   return nil;
197 }
198 - (NSString *)stringForDirParameter:(icalparameter *)_pa {
199   const char *s;
200   
201   if ((s = icalparameter_get_dir(_pa)))
202     return [StrClass stringWithCString:s];
203   return nil;
204 }
205 - (NSString *)stringForRoleParameter:(icalparameter *)_pa {
206   switch (icalparameter_get_role(_pa)) {
207   case ICAL_ROLE_CHAIR:
208     return @"CHAIR";
209   case ICAL_ROLE_REQPARTICIPANT:
210     return @"REQ-PART";
211   case ICAL_ROLE_OPTPARTICIPANT:
212     return @"OPT-PART";
213   case ICAL_ROLE_NONPARTICIPANT:
214     return @"NON-PART";
215     
216   case ICAL_ROLE_NONE:
217   case ICAL_ROLE_X:
218   default:
219     return nil;
220   }
221 }
222
223 - (NSString *)stringForPartStatParameter:(icalparameter *)_pa {
224   switch (icalparameter_get_partstat(_pa)) {
225   case ICAL_PARTSTAT_NEEDSACTION: return @"NEEDSACTION";
226   case ICAL_PARTSTAT_ACCEPTED:    return @"ACCEPTED";
227   case ICAL_PARTSTAT_DECLINED:    return @"DECLINED";
228   case ICAL_PARTSTAT_TENTATIVE:   return @"TENTATIVE";
229   case ICAL_PARTSTAT_DELEGATED:   return @"DELEGATED";
230   case ICAL_PARTSTAT_COMPLETED:   return @"COMPLETED";
231   case ICAL_PARTSTAT_INPROCESS:   return @"INPROCESS";
232     
233   case ICAL_PARTSTAT_X:
234   case ICAL_PARTSTAT_NONE:
235   default:
236     return nil;
237   }
238 }
239
240 - (NSString *)stringForRsvpParameter:(icalparameter *)_pa {
241   switch (icalparameter_get_rsvp(_pa)) {
242   case ICAL_RSVP_TRUE:  return @"true";
243   case ICAL_RSVP_FALSE: return @"false";
244     
245   case ICAL_RSVP_X:
246   case ICAL_RSVP_NONE:
247   default:
248     return nil;
249   }
250 }
251
252 - (id<SaxAttributes>)attrsForPropertyParameters:(icalproperty *)_p {
253   /*
254    2.7 Mapping Property Parameters to XML
255
256    The property parameters defined in the standard iCalendar format are
257    represented in the XML representation as an attribute on element
258    types.  The following table specifies the attribute name
259    corresponding to each property parameter.
260
261       +----------------+----------------+-----------+-----------------+
262       | Property       | Attribute      | Attribute | Default         |
263       | Parameter Name | Name           | Type      | Value           |
264       +----------------+----------------+-----------+-----------------+
265       | ALTREP         | altrep         | ENTITY    | IMPLIED         |
266       | CN             | cn             | CDATA     | Null String     |
267       | CUTYPE         | cutype         | NMTOKEN   | INDIVIDUAL      |
268       | DELEGATED-FROM | delegated-from | CDATA     | IMPLIED         |
269       | DELEGATED-TO   | delegated-to   | CDATA     | IMPLIED         |
270       | DIR            | dir            | ENTITY    | IMPLIED         |
271       | ENCODING       | Not Used       | n/a       | n/a             |
272       | FMTTYPE        | fmttype        | CDATA     | REQUIRED        |
273       | FBTYPE         | fbtype         | NMTOKEN   | BUSY            |
274       | LANGUAGE       | language       | CDATA     | IMPLIED         |
275       | MEMBER         | member         | CDATA     | IMPLIED         |
276       | PARTSTAT       | partstat       | NMTOKEN   | NEEDS-ACTION    |
277       | RANGE          | range          | NMTOKEN   | THISONLY        |
278       | RELATED        | related        | NMTOKEN   | START           |
279       | RELTYPE        | reltype        | NMTOKEN   | PARENT          |
280       | ROLE           | role           | NMTOKEN   | REQ-PARTICIPANT |
281       | RSVP           | rsvp           | NMTOKEN   | FALSE           |
282       | SENT-BY        | sent-by        | CDATA     | IMPLIED         |
283       | TZID           | tzid           | CDATA     | IMPLIED         |
284       | VALUE          | value          | NOTATION  | See elements    |
285       +----------------+----------------+-----------+-----------------+
286
287    The inline "ENCODING" property parameter is not needed in the XML
288    representation.  Inline binary information is always included as
289    parsable character data, after first being encoded using the BASE64
290    encoding of [RFC 2045].
291    */
292   icalparameter *pa;
293   int s = 0;
294   
295   for (pa = icalproperty_get_first_parameter(_p, ICAL_ANY_PARAMETER);
296        pa != NULL;
297        pa = icalproperty_get_next_parameter(_p, ICAL_ANY_PARAMETER)) {
298     NSString   *v = nil;
299     NSString   *attrName = nil;
300     
301     switch (icalparameter_isa(pa)) {
302     case ICAL_VALUE_PARAMETER:
303       attrName = @"value";
304       v = [self stringForValueParameter:pa];
305       break;
306       
307     case ICAL_TZID_PARAMETER:
308       attrName = @"tzid";
309       v = [self stringForTzIdParameter:pa];
310       break;
311       
312     case ICAL_X_PARAMETER:
313       attrName = [StrClass stringWithFormat:@"x-unknown-%i", s++];
314       v = [self stringForXParameter:pa];
315       break;
316     case ICAL_CN_PARAMETER:
317       attrName = @"cn";
318       v = [self stringForCnParameter:pa];
319       break;
320     case ICAL_DIR_PARAMETER:
321       attrName = @"dir";
322       v = [self stringForDirParameter:pa];
323       break;
324     case ICAL_RSVP_PARAMETER:
325       attrName = @"rsvp";
326       v = [self stringForRsvpParameter:pa];
327       break;
328     case ICAL_PARTSTAT_PARAMETER:
329       attrName = @"partstat";
330       v = [self stringForPartStatParameter:pa];
331       break;
332     case ICAL_ROLE_PARAMETER:
333       attrName = @"role";
334       v = [self stringForRoleParameter:pa];
335       break;
336       
337     default:
338       [self logUnknownParameterKind:pa];
339       break;
340     }
341     
342     if (attrName == nil) continue;
343     if (v        == nil) continue;
344     
345     if (self->attrs == nil)
346       self->attrs = [[SaxAttributes alloc] init];
347
348     [self->attrs
349          addAttribute:attrName uri:XMLNS_XCAL_01 rawName:attrName 
350          type:@"CDATA"
351          value:v];
352   }
353   
354   return attrs;
355 }
356
357 - (void)handleErrorProperty:(icalproperty *)_p
358   ofComponent:(icalcomponent *)_comp 
359 {
360   switch (icalproperty_isa(_p)) {
361   case ICAL_XLICERROR_PROPERTY:
362     NSLog(@"iCalSaxDriver: found an error property: %s",
363           icalproperty_get_xlicerror(_p));
364     break;
365     
366   default:
367     NSLog(@"iCalSaxDriver: found an unknown error property !");
368     break;
369   }
370 }
371
372 - (void)walkProperty:(icalproperty *)_p ofComponent:(icalcomponent *)_comp {
373   /*
374 2.9 Mapping Component Properties to XML
375
376    Component properties in the standard iCalendar format provide
377    calendar information about the calendar component.  The component
378    properties defined in the standard iCalendar format are represented
379    in the XML representation as element types.  The following tables
380    specify the element types corresponding to each of the properties in
381    the specified property category.
382
383       Descriptive Component Properties
384       +----------------+-------------+-----------------------------+
385       | Component      | Element     | Element Content Model       |
386       | Property Name  | Name        |                             |
387       +----------------+-------------+-----------------------------+
388       | ATTACH         | attach      | extref or b64bin            |
389       |                | extref      | EMPTY                       |
390       |                | b64bin      | PCDATA                      |
391       | CATEGORIES     | categories  | Any number of item elements |
392       |                | item        | PCDATA                      |
393       | CLASS          | class       | PCDATA                      |
394       | COMMENT        | comment     | PCDATA                      |
395       | DESCRIPTION    | description | PCDATA                      |
396       | GEO            | geo         | lat followed by lon element |
397       |                | lat         | PCDATA                      |
398       |                | lon         | PCDATA                      |
399       | LOCATION       | location    | PCDATA                      |
400       | PERCENT        | percent     | PCDATA                      |
401       | PRIORITY       | priority    | PCDATA                      |
402       | RESOURCES      | resources   | Any number of item elements |
403       | STATUS         | status      | PCDATA                      |
404       | SUMMARY        | summary     | PCDATA                      |
405       +----------------+-------------+-----------------------------+
406       Date and Time Component Properties
407       +----------------+------------+-----------------------------+
408       | Component      | Element    | Element Content Model       |
409       | Property Name  | Name       |                             |
410       +----------------+------------+-----------------------------+
411       | COMPLETED      | completed  | PCDATA                      |
412       | DTEND          | dtend      | PCDATA                      |
413       | DUE            | due        | PCDATA                      |
414       | DTSTART        | dtstart    | PCDATA                      |
415       | DURATION       | duration   | PCDATA                      |
416       | FREEBUSY       | freebusy   | PCDATA                      |
417       | TRANSP         | transp     | PCDATA                      |
418       +----------------+------------+-----------------------------+
419
420
421       Time Zone Component Properties
422       +----------------+-------------+-----------------------------+
423       | Component      | Element     | Element Content Model       |
424       | Property Name  | Name        |                             |
425       +----------------+-------------+-----------------------------+
426       | TZID           | tzid        | PCDATA                      |
427       | TZNAME         | tzname      | PCDATA                      |
428       | TZOFFSETFROM   | tzoffsetfrom| PCDATA                      |
429       | TZOFFSETTO     | tzoffsetto  | PCDATA                      |
430       | TZURL          | tzurl       | EMPTY                       |
431       +----------------+-------------+-----------------------------+
432
433
434       Relationship Component Properties
435       +----------------+---------------+--------------------------+
436       | Component      | Element       | Element Content Model    |
437       | Property Name  | Name          |                          |
438       +----------------+---------------+--------------------------+
439       | ATTENDEE       | attendee      | PCDATA                   |
440       | CONTACT        | contact       | PCDATA                   |
441       | ORGANIZER      | organizer     | PCDATA                   |
442       | RECURRENCE-ID  | recurrence-id | PCDATA                   |
443       | RELATED-TO     | related-to    | PCDATA                   |
444       | URL            | url           | EMPTY                    |
445       | UID            | uid           | PCDATA                   |
446       +----------------+---------------+--------------------------+
447
448
449       Recurrence Component Properties
450       +----------------+------------+-----------------------------+
451       | Component      | Element    | Element Content Model       |
452       | Property Name  | Name       |                             |
453       +----------------+------------+-----------------------------+
454       | EXDATE         | exdate     | PCDATA                      |
455       | EXRULE         | exrule     | PCDATA                      |
456       | RDATE          | rdate      | PCDATA                      |
457       | RRULE          | rrule      | PCDATA                      |
458       +----------------+------------+-----------------------------+
459
460
461       Alarm Component Properties
462       +----------------+------------+-----------------------------+
463       | Component      | Element    | Element Content Model       |
464       | Property Name  | Name       |                             |
465       +----------------+------------+-----------------------------+
466       | ACTION         | action     | PCDATA                      |
467       | REPEAT         | repeat     | PCDATA                      |
468       | TRIGGER        | trigger    | PCDATA                      |
469       +----------------+------------+-----------------------------+
470
471
472       Change Management Component Properties
473       +----------------+---------------+--------------------------+
474       | Component      | Element       | Element Content Model    |
475       | Property Name  | Name          |                          |
476       +----------------+---------------+--------------------------+
477       | CREATED        | created       | PCDATA                   |
478       | DTSTAMP        | dtstamp       | PCDATA                   |
479       | LAST-MODIFIED  | last-modified | PCDATA                   |
480       | SEQUENCE       | sequence      | PCDATA                   |
481       +----------------+---------------+--------------------------+
482
483
484       Miscellaneous Component Properties
485       +----------------+----------------+-------------------------+
486       | Component      | Element        | Element Content Model   |
487       | Property Name  | Name           |                         |
488       +----------------+----------------+-------------------------+
489       | REQUEST-STATUS | request-status | PCDATA                  |
490       +----------------+----------------+-------------------------+
491   */
492   NSString *tagName = nil;
493   
494   if (_p == NULL) return;
495   
496   switch (icalproperty_isa(_p)) {
497   case ICAL_ACTION_PROPERTY:        tagName = @"action";         break;
498   case ICAL_ATTENDEE_PROPERTY:      tagName = @"attendee";       break;
499   case ICAL_CLASS_PROPERTY:         tagName = @"class";          break;
500   case ICAL_COMMENT_PROPERTY:       tagName = @"comment";        break;
501   case ICAL_COMPLETED_PROPERTY:     tagName = @"completed";      break;
502   case ICAL_CONTACT_PROPERTY:       tagName = @"contact";        break;
503   case ICAL_CREATED_PROPERTY:       tagName = @"created";        break;
504   case ICAL_DESCRIPTION_PROPERTY:   tagName = @"description";    break;
505   case ICAL_DTEND_PROPERTY:         tagName = @"dtend";          break;
506   case ICAL_DTSTAMP_PROPERTY:       tagName = @"dtstamp";        break;
507   case ICAL_DTSTART_PROPERTY:       tagName = @"dtstart";        break;
508   case ICAL_DUE_PROPERTY:           tagName = @"due";            break;
509   case ICAL_DURATION_PROPERTY:      tagName = @"duration";       break;
510   case ICAL_FREEBUSY_PROPERTY:      tagName = @"freebusy";       break;
511   case ICAL_EXDATE_PROPERTY:        tagName = @"exdate";         break;
512   case ICAL_EXRULE_PROPERTY:        tagName = @"exrule";         break;
513   case ICAL_RDATE_PROPERTY:         tagName = @"rdate";          break;
514   case ICAL_RRULE_PROPERTY:         tagName = @"rrule";          break;
515   case ICAL_LASTMODIFIED_PROPERTY:  tagName = @"last-modified";  break;
516   case ICAL_LOCATION_PROPERTY:      tagName = @"location";       break;
517   case ICAL_ORGANIZER_PROPERTY:     tagName = @"organizer";      break;
518   case ICAL_PRIORITY_PROPERTY:      tagName = @"priority";       break;
519   case ICAL_REPEAT_PROPERTY:        tagName = @"repeat";         break;
520   case ICAL_RELATEDTO_PROPERTY:     tagName = @"related-to";     break;
521   case ICAL_RECURRENCEID_PROPERTY:  tagName = @"recurrence-id";  break;
522   case ICAL_REQUESTSTATUS_PROPERTY: tagName = @"request-status"; break;
523   case ICAL_SEQUENCE_PROPERTY:      tagName = @"sequence";       break;
524   case ICAL_STATUS_PROPERTY:        tagName = @"status";         break;
525   case ICAL_SUMMARY_PROPERTY:       tagName = @"summary";        break;
526   case ICAL_TRANSP_PROPERTY:        tagName = @"transp";         break;
527   case ICAL_TRIGGER_PROPERTY:       tagName = @"trigger";        break;
528   case ICAL_TZID_PROPERTY:          tagName = @"tzid";           break;
529   case ICAL_TZNAME_PROPERTY:        tagName = @"tzname";         break;
530   case ICAL_TZOFFSETFROM_PROPERTY:  tagName = @"tzoffsetfrom";   break;
531   case ICAL_TZOFFSETTO_PROPERTY:    tagName = @"tzoffsetto";     break;
532   case ICAL_TZURL_PROPERTY:         tagName = @"tzurl";          break;
533   case ICAL_UID_PROPERTY:           tagName = @"uid";            break;
534   case ICAL_URL_PROPERTY:           tagName = @"url";            break;
535   case ICAL_PERCENTCOMPLETE_PROPERTY:  
536     tagName = @"percent-complete";        
537     break;
538     
539     /* special handling in xcal !! check ! */
540
541   case ICAL_GEO_PROPERTY:
542     tagName = @"geo";   /* lat followed by lon element */
543     //tagName = @"lat";
544     //tagName = @"lon";
545     break;
546     
547   case ICAL_RESOURCES_PROPERTY:    
548     /* content model: Any number of item elements */
549     tagName = @"resources";        
550     break;
551   case ICAL_CATEGORIES_PROPERTY:    
552     /* content model: Any number of item elements */
553     tagName = @"categories";        
554     break;
555
556   case ICAL_ATTACH_PROPERTY: 
557     tagName = @"attach";    /* content model: extref || b64bin */
558     // tagName = @"extref"; /* content model: empty  */
559     // tagName = @"b64bin"; /* content model: pcdata */
560     break;
561     
562     /* attribute properties */
563   case ICAL_PRODID_PROPERTY:
564   case ICAL_METHOD_PROPERTY:
565   case ICAL_VERSION_PROPERTY:
566   case ICAL_CALSCALE_PROPERTY:
567     return;
568     
569     /* extension properties */
570   case ICAL_XLICERROR_PROPERTY:
571     [self handleErrorProperty:_p ofComponent:_comp];
572     return;
573     
574   case ICAL_X_PROPERTY:
575     tagName = [StrClass stringWithCString:icalproperty_get_x_name(_p)];
576     break;
577     
578   default:
579     [self logUnknownPropertyKind:_p];
580     return;
581   }
582   
583   [self->attrs clear];
584   [self->contentHandler
585        startElement:tagName
586        namespace:XMLNS_XCAL_01
587        rawName:tagName
588        attributes:[self attrsForPropertyParameters:_p]];
589   [self->attrs clear];
590   
591   switch (icalproperty_isa(_p)) {
592     case ICAL_RRULE_PROPERTY: {
593       /* this should unparse the rrule parameters as-is */
594       NSLog(@"iCalSaxDriver: recurrence rules are not properly handled yet");
595       break;
596     }
597     
598     default:
599       /* generic handling for value: pass them as SAX characters ... */
600       [self walkValue:icalproperty_get_value(_p) 
601             ofProperty:_p inComponent:_comp];
602   }
603   
604   [self->contentHandler
605        endElement:tagName
606        namespace:XMLNS_XCAL_01
607        rawName:tagName];
608 }
609
610 - (id<SaxAttributes>)attrPropertiesOfComponent:(icalcomponent *)_comp {
611   icalproperty *p;
612   
613   for (p = icalcomponent_get_first_property(_comp,ICAL_ANY_PROPERTY);
614        p != nil;
615        p = icalcomponent_get_next_property(_comp,ICAL_ANY_PROPERTY)) {
616     NSString   *v = nil;
617     NSString   *attrName = nil;
618     
619     switch (icalproperty_isa(p)) {
620     case ICAL_PRODID_PROPERTY:    attrName = @"prodid";   break;
621     case ICAL_METHOD_PROPERTY:    attrName = @"method";   break;
622     case ICAL_VERSION_PROPERTY:   attrName = @"version";  break;
623     case ICAL_CALSCALE_PROPERTY:  attrName = @"calscale"; break;
624     default:
625       continue;
626     }
627     
628     if (attrName == nil) continue;
629     
630     v = [self stringForValue:icalproperty_get_value(p)
631               ofProperty:p
632               inComponent:_comp];
633     
634     if (v == nil) continue;
635     
636     if (self->attrs == nil)
637       self->attrs = [[SaxAttributes alloc] init];
638     
639     [self->attrs
640          addAttribute:attrName uri:XMLNS_XCAL_01 rawName:attrName 
641          type:@"CDATA"
642          value:v];
643   }
644   return self->attrs;
645 }
646
647 - (void)walkPropertiesOfComponent:(icalcomponent *)_comp {
648   icalproperty *p;
649   
650   for (p = icalcomponent_get_first_property(_comp,ICAL_ANY_PROPERTY);
651        p != nil;
652        p = icalcomponent_get_next_property(_comp,ICAL_ANY_PROPERTY)) {
653     [self walkProperty:p ofComponent:_comp];
654   }
655 }
656
657 - (void)walkSubComponents:(icalcomponent *)_comp {
658   icalcomponent *c;
659   
660   for(c = icalcomponent_get_first_component(_comp, ICAL_ANY_COMPONENT);
661       c != NULL;
662       c = icalcomponent_get_next_component(_comp, ICAL_ANY_COMPONENT)) {
663     [self walkComponent:c];
664   }
665 }
666
667 - (void)walkComponent:(icalcomponent *)_comp {
668   /*
669       Component Structuring Properties
670       +----------------+------------+-------------------------------+
671       | Component      | Element    | Element Content Model         |
672       | Property Name  | Name       |                               |
673       +----------------+------------+-------------------------------+
674       | Multiple iCal- | iCalendar  | One or more iCal elements     |
675       | endar objects  |            |                               |
676       | VCALENDAR      | vcalendar  | calcomp parameter entity      |
677       | VEVENT         | vevent     | vevent.opt1 and vevent.optm   |
678       |                |            | parameter entity and valarm   |
679       |                |            | element                       |
680       | VTODO          | vtodo      | vtodo.opt1 and vtodo.optm     |
681       |                |            | parameter entity and valarm   |
682       |                |            | element                       |
683       | VJOURNAL       | vjournal   | vjournal.opt1 and             |
684       |                |            | vjournal.optm parameter       |
685       |                |            | entity                        |
686       | VFREEBUSY      | vfreebusy  | vfreebusy.opt1 and            |
687       |                |            | vfreebusy.optm parameter      |
688       |                |            | entity                        |
689       | VTIMEZONE      | vtimezone  | vtimezone.man,                |
690       |                |            | vtimezone.opt1,               |
691       |                |            | vtimezone.mann parameter      |
692       |                |            | entity                        |
693       | STANDARD       | standard   | standard.man or standard.optm |
694       |                |            | entity                        |
695       | DAYLIGHT       | daylight   | daylight.man or daylight.optm |
696       |                |            | entity                        |
697       | VALARM         | valarm     | valarm.audio, valarm.display, |
698       |                |            | valarm.email and              |
699       |                |            | valarm.procedure entity       |
700       +----------------+------------+-------------------------------+
701   */
702   NSString *tagName = nil;
703   
704   switch (icalcomponent_isa(_comp)) {
705     case ICAL_VCALENDAR_COMPONENT:
706       tagName = @"vcalendar";
707       break;
708     case ICAL_VFREEBUSY_COMPONENT:
709       tagName = @"vfreebusy";
710       break;
711     case ICAL_VEVENT_COMPONENT:
712       tagName = @"vevent";
713       break;
714     case ICAL_VTODO_COMPONENT:
715       tagName = @"vtodo";
716       break;
717     case ICAL_VJOURNAL_COMPONENT:
718       tagName = @"vjournal";
719       break;
720     case ICAL_VTIMEZONE_COMPONENT:
721       tagName = @"vtimezone";
722       break;
723     case ICAL_VALARM_COMPONENT:
724       tagName = @"valarm";
725       break;
726
727     case ICAL_XSTANDARD_COMPONENT:
728       tagName = @"standard"; // in xcal as "standard" component ?
729       break;
730     case ICAL_XDAYLIGHT_COMPONENT:
731       tagName = @"daylight"; // in xcal as "daylight" component ?
732       break;
733       
734     case ICAL_XROOT_COMPONENT:
735       /* root component for a file with multiple components */
736       tagName = @"iCalendar";
737       break;
738       
739     default:
740       [self logUnknownComponentKind:_comp];
741       return;
742   }
743   
744   /*
745     attributes:
746       calscale - cdata   - implied
747       method   - nmtoken - publish
748       version  - cdata   - required
749       prodid   - cdata   - implied
750
751     vcalendar: language - cdata - implied
752   */
753   
754   [self->attrs clear];
755   [self->contentHandler
756        startElement:tagName
757        namespace:XMLNS_XCAL_01
758        rawName:tagName
759        attributes:[self attrPropertiesOfComponent:_comp]];
760   [self->attrs clear];
761
762   [self walkPropertiesOfComponent:_comp];
763   
764   [self walkSubComponents:_comp];
765   
766   [self->contentHandler
767        endElement:tagName
768        namespace:XMLNS_XCAL_01
769        rawName:tagName];
770 }
771
772 - (void)walkRootComponent:(icalcomponent *)_comp {
773   [self->contentHandler startDocument];
774   [self->contentHandler startPrefixMapping:@"" uri:XMLNS_XCAL_01];
775   
776   [self walkComponent:_comp];
777   
778   [self->contentHandler endPrefixMapping:@""];
779   [self->contentHandler endDocument];
780 }
781
782 /* parsing */
783
784 - (void)_reportICalErrno:(icalerrorenum)_errcode {
785   switch (_errcode) {
786   case ICAL_NO_ERROR:
787     /* well, no error ... */
788     break;
789     
790   case ICAL_BADARG_ERROR:
791   case ICAL_NEWFAILED_ERROR:
792   case ICAL_ALLOCATION_ERROR:
793   case ICAL_MALFORMEDDATA_ERROR:
794   case ICAL_PARSE_ERROR:
795   case ICAL_INTERNAL_ERROR:
796     /* Like assert --internal consist. prob */
797   case ICAL_FILE_ERROR:
798   case ICAL_USAGE_ERROR:
799   case ICAL_UNIMPLEMENTED_ERROR:
800   case ICAL_UNKNOWN_ERROR:
801     /* Used for problems in input to icalerror_strerror()*/
802     
803   default: {
804     SaxParseException *e;
805     const char *errstr;
806     NSString   *s;
807     
808     errstr = icalerror_strerror(_errcode);
809     if (debugOn) {
810       NSLog(@"%s: generic ical parsing error(code=%i): %s",
811             __PRETTY_FUNCTION__, _errcode, errstr);
812     }
813     s = [[StrClass alloc] initWithFormat:@"generic libical error %i: %s",
814                             _errcode, errstr];
815     e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
816                                reason:s
817                                userInfo:nil];
818     [s release];
819     [self->errorHandler fatalError:e];
820     break;
821   }
822   }
823 }
824
825 - (BOOL)parseString:(NSString *)_string {
826   icalcomponent *c;
827   const char    *str;
828
829   if (debugOn) {
830     NSLog(@"%s: parse string (len=%i)", __PRETTY_FUNCTION__, 
831           [_string length]);
832   }
833   if (_string == nil) {
834     if (debugOn) NSLog(@"%s:   got no string ...", __PRETTY_FUNCTION__);
835     return NO;
836   }
837   if ((str = [_string icalCString]) == NULL) {
838     if (debugOn) NSLog(@"%s:   got no icalCString ...", __PRETTY_FUNCTION__);
839     return NO;
840   }
841
842   // printf("STR: '%s'(%i)\n", str, strlen(str));
843   
844   if ((c = icalparser_parse_string(str)) == NULL) {
845     /* parsing failed ... */
846     if (debugOn)
847       NSLog(@"%s:   libical parsing failed ...", __PRETTY_FUNCTION__);
848     [self _reportICalErrno:icalerrno];
849     return NO;
850   }
851   
852   [self walkRootComponent:c];
853   
854   return YES;
855 }
856 - (BOOL)parseData:(NSData *)_data {
857   icalcomponent *c;
858   unsigned char *str;
859   unsigned len;
860   
861   if (_data == nil) return NO;
862
863   len = [_data length];
864   str = malloc(len + 10);
865   [_data getBytes:str length:len];
866   str[len] = '\0';
867   
868   if ((c = icalparser_parse_string(str)) == NULL)
869     /* parsing failed ... */
870     return NO;
871   
872   [self walkRootComponent:c];
873   
874   return YES;
875 }
876
877 - (void)parseFileAtPath:(NSString *)_path encoding:(NSStringEncoding)_enc {
878   NSAutoreleasePool *pool;
879   NSData   *data;
880   NSString *s;
881
882   if (debugOn)
883     NSLog(@"%s: parse file: %@", __PRETTY_FUNCTION__, _path);
884   
885   pool = [[NSAutoreleasePool alloc] init];
886   
887   data = [[NSData alloc] initWithContentsOfMappedFile:_path];
888   if (data == nil) {
889     /* throw Sax Exception ! */
890     if (debugOn)
891       NSLog(@"%s:   could not map data: %@", __PRETTY_FUNCTION__, _path);
892     return;
893   }
894   
895   s = [[[StrClass alloc] initWithData:data encoding:_enc] autorelease];
896   [data release];
897   
898   [self parseString:s];
899   
900   [pool release];
901 }
902 - (void)parseFileAtPath:(NSString *)_path {
903   [self parseFileAtPath:_path encoding:NSUTF8StringEncoding];
904 }
905
906 /* SaxXMLReader */
907
908 /* features & properties */
909
910 - (void)setFeature:(NSString *)_name to:(BOOL)_value {
911 }
912 - (BOOL)feature:(NSString *)_name {
913   return NO;
914 }
915 - (void)setProperty:(NSString *)_name to:(id)_value {
916 }
917 - (id)property:(NSString *)_name {
918   return nil;
919 }
920
921 /* handlers */
922
923 - (void)setContentHandler:(id<NSObject,SaxContentHandler>)_handler {
924   ASSIGN(self->contentHandler, _handler);
925 }
926 - (void)setDTDHandler:(id<NSObject,SaxDTDHandler>)_handler {
927 }
928 - (void)setErrorHandler:(id<NSObject,SaxErrorHandler>)_handler {
929   ASSIGN(self->errorHandler, _handler);
930 }
931 - (void)setEntityResolver:(id<NSObject,SaxEntityResolver>)_handler {
932 }
933 - (id<NSObject,SaxContentHandler>)contentHandler {
934   return self->contentHandler;
935 }
936 - (id<NSObject,SaxDTDHandler>)dtdHandler {
937   return nil;
938 }
939 - (id<NSObject,SaxErrorHandler>)errorHandler {
940   return self->errorHandler;
941 }
942 - (id<NSObject,SaxEntityResolver>)entityResolver {
943   return nil;
944 }
945
946 /* parsing */
947
948 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
949   if (debugOn)
950     NSLog(@"%s: parse(%@): %@", __PRETTY_FUNCTION__, _sysId, _source);
951   
952   if (_source == nil) {
953     /* no source ??? */
954     if (debugOn) NSLog(@"%s: not source: %@)", __PRETTY_FUNCTION__, _sysId);
955     return;
956   }
957
958   if ([_source isKindOfClass:StrClass]) {
959     /* convert strings to UTF8 data */
960     if (_sysId == nil) _sysId = @"<string>";
961     if (debugOn) {
962       NSLog(@"%s:   parse string (len=%i))", __PRETTY_FUNCTION__, 
963             [_source length]);
964     }
965     [self parseString:_source];
966     return;
967   }
968   
969   if ([_source isKindOfClass:[NSURL class]]) {
970     if (_sysId == nil) _sysId = [_source absoluteString];
971     if (debugOn) NSLog(@"%s:  load URL: %@", __PRETTY_FUNCTION__, _source);
972     _source = [_source resourceDataUsingCache:NO];
973   }
974   else if ([_source isKindOfClass:[NSData class]]) {
975     if (_sysId == nil) _sysId = @"<data>";
976   }
977   else {
978     SaxParseException *e;
979     NSDictionary      *ui;
980     
981     if (debugOn) {
982       NSLog(@"%s:  unknown source class: %@", __PRETTY_FUNCTION__, 
983             NSStringFromClass([_source class]));
984     }
985     
986     ui = [NSDictionary dictionaryWithObjectsAndKeys:
987                          _source ? _source : @"<nil>", @"source",
988                          self,                         @"parser",
989                          nil];
990     
991     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
992                                reason:@"cannot handle datasource"
993                                userInfo:ui];
994     
995     [self->errorHandler fatalError:e];
996     return;
997   }
998   
999   if (debugOn) {
1000     NSLog(@"%s:   parse data (len=%i))", __PRETTY_FUNCTION__, 
1001           [_source length]);
1002   }
1003   [self parseData:_source];
1004 }
1005
1006 - (void)parseFromSource:(id)_source {
1007   [self parseFromSource:_source systemId:nil];
1008 }
1009
1010 - (void)parseFromSystemId:(NSString *)_sysId {
1011   if (debugOn)
1012     NSLog(@"%s: parse ID: %@", __PRETTY_FUNCTION__, _sysId);
1013   
1014   if (![_sysId hasPrefix:@"file:"]) {
1015     SaxParseException *e;
1016     NSDictionary      *ui;
1017     NSURL *url;
1018     
1019     if ((url = [NSURL URLWithString:_sysId])) {
1020       [self parseFromSource:url systemId:_sysId];
1021       return;
1022     }
1023
1024     if (debugOn)
1025       NSLog(@"%s:   could not parse ID: %@", __PRETTY_FUNCTION__, _sysId);
1026   
1027     ui = [NSDictionary dictionaryWithObjectsAndKeys:
1028                          _sysId ? _sysId : @"<nil>", @"systemID",
1029                          self,                       @"parser",
1030                          nil];
1031     
1032     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1033                                reason:@"cannot handle system-id"
1034                                userInfo:ui];
1035     
1036     [self->errorHandler fatalError:e];
1037     return;
1038   }
1039
1040   /* cut off file:// */
1041   if ([_sysId hasPrefix:@"file://"])
1042     _sysId = [_sysId substringFromIndex:7];
1043   else
1044     _sysId = [_sysId substringFromIndex:5];
1045   
1046   if (debugOn)
1047     NSLog(@"%s:   parse file: %@", __PRETTY_FUNCTION__, _sysId);
1048   [self parseFileAtPath:_sysId];
1049 }
1050
1051 @end /* ICalSaxParser */
1052
1053 @interface iCalSaxDriver : ICalSaxParser
1054 @end
1055
1056 @implementation iCalSaxDriver
1057 @end
1058
1059 #include "unicode.h"