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